/****************************************************************************
 * drivers/video/isx012.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/mutex.h>
#include <nuttx/arch.h>
#include <nuttx/signal.h>
#include <nuttx/fs/fs.h>
#include <nuttx/i2c/i2c_master.h>
#include <arch/board/board.h>
#include <arch/irq.h>

#include <nuttx/video/isx012.h>
#include "isx012_reg.h"
#include "isx012_range.h"
#include <nuttx/video/imgsensor.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* The following macro is enabled because
 * it is to make stable startup. (other case)
 */

/* #define ISX012_NOT_USE_NSTBY */

/* The following macro is disabled because it is to see detailed control. */

/* #define ISX012_CHECK_IN_DETAIL */

/* Skip invalid frame because it occurs first due to the spec of isx012. */

#define ISX012_FRAME_SKIP_EN

#define OUT_HSIZE_QVGA           (320)
#define OUT_VSIZE_QVGA           (240)
#define OUT_HSIZE_VGA            (640)
#define OUT_VSIZE_VGA            (480)
#define OUT_HSIZE_HD            (1280)
#define OUT_VSIZE_HD             (720)
#define OUT_HSIZE_QUADVGA       (1280)
#define OUT_VSIZE_QUADVGA        (960)
#define OUT_HSIZE_FULLHD        (1920)
#define OUT_VSIZE_FULLHD        (1080)
#define OUT_HSIZE_3M            (2048)
#define OUT_VSIZE_3M            (1536)
#define OUT_HSIZE_5M            (2560)
#define OUT_VSIZE_5M            (1920)

#define OUT_YUV_VSIZE_MIN         (64)
#define OUT_YUV_HSIZE_MIN         (96)
#define OUT_JPG_VSIZE_MIN         (64)
#define OUT_JPG_HSIZE_MIN         (96)
#define OUT_YUV_15FPS_VSIZE_MAX  (600)
#define OUT_YUV_15FPS_HSIZE_MAX  (800)
#define OUT_YUV_30FPS_VSIZE_MAX  (600)
#define OUT_YUV_30FPS_HSIZE_MAX  (800)
#define OUT_YUV_60FPS_VSIZE_MAX  (480)
#define OUT_YUV_60FPS_HSIZE_MAX  (640)
#define OUT_YUV_120FPS_VSIZE_MAX (240)
#define OUT_YUV_120FPS_HSIZE_MAX (320)
#define OUT_JPG_15FPS_VSIZE_MAX (1944)
#define OUT_JPG_15FPS_HSIZE_MAX (2592)
#define OUT_JPG_30FPS_VSIZE_MAX  (960)
#define OUT_JPG_30FPS_HSIZE_MAX (1280)
#define OUT_JPG_60FPS_VSIZE_MAX  (480)
#define OUT_JPG_60FPS_HSIZE_MAX  (640)
#define OUT_JPG_120FPS_VSIZE_MAX (240)
#define OUT_JPG_120FPS_HSIZE_MAX (320)

#define OUT_YUVINT_30FPS_VSIZE_MAX  (240)
#define OUT_YUVINT_30FPS_HSIZE_MAX  (400)
#define OUT_JPGINT_30FPS_VSIZE_MAX  (960)
#define OUT_JPGINT_30FPS_HSIZE_MAX (1280)
#define OUT_JPGINT_15FPS_VSIZE_MAX (1224)
#define OUT_JPGINT_15FPS_HSIZE_MAX (1632)

#define VINT_TIMEOUT                (400) /* ms */
#define VINT_WAIT_TIME                (5) /* ms */
#define VINT_DELAY_TIME               (0) /* ms */
#define CAMERA_MODE_TIMEOUT         (800) /* ms */
#define CAMERA_MODE_WAIT_TIME        (10) /* ms */
#define CAMERA_MODE_DELAY_TIME        (0) /* ms */
#define DEVICE_STATE_TIMEOUT        (100) /* ms */
#define DEVICE_STATE_WAIT_TIME        (1) /* ms */
#define DEVICE_STATE_DELAY_TIME       (2) /* ms */

#define I2CFREQ_STANDARD         (100000) /* Standard mode : 100kHz */
#define I2CFREQ_FAST             (400000) /* Fast mode     : 400kHz */

#define ISX012_SIZE_STEP              (2)

#define CXC_RGB_DATA_UNIT_NUM        (27)
#define CXC_RGB_DATA_UNIT_SIZE        (7)
#define CXC_GRB_DATA_UNIT_NUM        (27)
#define CXC_GRB_DATA_UNIT_SIZE        (7)
#define SHD_RGB_DATA_UNIT_NUM        (27)
#define SHD_RGB_DATA_UNIT_SIZE       (11)
#define SHD_GRB_DATA_UNIT_NUM        (27)
#define SHD_GRB_DATA_UNIT_SIZE       (11)
#define SHD_R1_DATA_UNIT_NUM         (14)
#define SHD_R1_DATA_UNIT_SIZE        (11)
#define SHD_R2_DATA_UNIT_NUM         (14)
#define SHD_R2_DATA_UNIT_SIZE        (11)
#define SHD_B2_DATA_UNIT_NUM         (14)
#define SHD_B2_DATA_UNIT_SIZE        (11)

#define ISX012_ELEMS_3APARAM         (3)

#define VALIDATE_VALUE(val, min, max, step) (((val >= min) && \
                                              (val <= max) && \
                                              (((val - min) % step) == 0) ? \
                                              OK : -ERANGE))

#define ISX012_CHIPID_L (0x0000c460)
#define ISX012_CHIPID_H (0x00005516)

#define BASE_HSIZE_FOR_CLIP_OFFSET (2592)
#define BASE_VSIZE_FOR_CLIP_OFFSET (1944)

#define ZOOM_UNIT        (0x0100)
#define CLIP_OFFSET_UNIT (0x0010)
#define CLIP_SIZE_UNIT   (8)
#define RESCALE_FOR_CLIP(v, a, b)  (((v) * (a)) / (b))

/* The number of whole image splits for spot position decision. */

#define ISX012_SPOT_POSITION_SPLIT_NUM_X (9)
#define ISX012_SPOT_POSITION_SPLIT_NUM_Y (7)

/****************************************************************************
 * Private Types
 ****************************************************************************/

enum isx012_state_e
{
  STATE_ISX012_PRESLEEP,
  STATE_ISX012_SLEEP,
  STATE_ISX012_ACTIVE,
  STATE_ISX012_POWEROFF,
};

typedef enum isx012_state_e isx012_state_t;

struct isx012_reg_s
{
  uint16_t regaddr;
  uint16_t regval;
  uint8_t  regsize;
};

typedef struct isx012_reg_s isx012_reg_t;

struct isx012_rect_s
{
  int32_t left;
  int32_t top;
  uint32_t width;
  uint32_t height;
};

typedef struct isx012_rect_s isx012_rect_t;

struct isx012_dev_s
{
  struct imgsensor_s      sensor;
  mutex_t                 i2c_lock;
  FAR struct i2c_master_s *i2c;        /* I2C interface */
  struct i2c_config_s     i2c_cfg;     /* I2C config */
  isx012_state_t          state;       /* ISX012 status */
  uint8_t                 mode;        /* ISX012 mode */
  isx012_rect_t           clip_video;  /* Clip information for VIDEO */
  isx012_rect_t           clip_still;  /* Clip information for STILL */
};

typedef struct isx012_dev_s isx012_dev_t;

#define ARRAY_NENTRIES(a) (sizeof(a)/sizeof(a[0]))

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* register operations */

static uint16_t isx012_getreg(FAR isx012_dev_t *priv,
                              uint16_t regaddr, uint16_t regsize);
static int     isx012_putreg(FAR isx012_dev_t *priv, uint16_t regaddr,
                             uint32_t regval, uint16_t regsize);
static int     isx012_putreglist(FAR isx012_dev_t *priv,
                         FAR const isx012_reg_t *reglist, size_t nentries);
#ifdef ISX012_CHECK_IN_DETAIL
static int     isx012_chipid(FAR struct i2c_master_s *i2c);
#endif

static int isx012_chk_int_state(FAR isx012_dev_t *priv,
                                uint8_t  sts, uint32_t delay_time,
                                uint32_t wait_time, uint32_t timeout);
static int isx012_set_mode_param(FAR isx012_dev_t *priv,
                                 imgsensor_stream_type_t type,
                                 uint8_t nr_fmt,
                                 FAR imgsensor_format_t *fmt,
                                 FAR imgsensor_interval_t *interval);
static int isx012_change_camera_mode(FAR isx012_dev_t *priv, uint8_t mode);
static int isx012_change_device_state(FAR isx012_dev_t *priv,
                                      isx012_state_t state);
static int isx012_replace_frameinterval_to_regval
                (FAR imgsensor_interval_t *interval);
static int8_t isx012_get_maximum_fps(uint8_t nr_datafmt,
                                     FAR imgsensor_format_t *datafmt);
static int isx012_set_shd(FAR isx012_dev_t *priv);
static bool is_movie_needed(uint8_t fmt, uint8_t fps);

/* image sensor device operations interface */

static bool isx012_is_available(FAR struct imgsensor_s *sensor);
static int isx012_init(FAR struct imgsensor_s *sensor);
static int isx012_uninit(FAR struct imgsensor_s *sensor);
static FAR const char *
isx012_get_driver_name(FAR struct imgsensor_s *sensor);
static int isx012_validate_frame_setting(FAR struct imgsensor_s *sensor,
                                         imgsensor_stream_type_t type,
                                         uint8_t nr_datafmt,
                                         FAR imgsensor_format_t *datafmts,
                                         FAR imgsensor_interval_t *interval);
static int isx012_start_capture(FAR struct imgsensor_s *sensor,
                                imgsensor_stream_type_t type,
                                uint8_t nr_datafmt,
                                FAR imgsensor_format_t *datafmts,
                                FAR imgsensor_interval_t *interval);
static int isx012_stop_capture(FAR struct imgsensor_s *sensor,
                               imgsensor_stream_type_t type);
static int isx012_get_supported_value(FAR struct imgsensor_s *sensor,
             uint32_t id, FAR imgsensor_supported_value_t *value);
static int isx012_get_value(FAR struct imgsensor_s *sensor,
             uint32_t id, uint32_t size, FAR imgsensor_value_t *value);
static int isx012_set_value(FAR struct imgsensor_s *sensor,
             uint32_t id, uint32_t size, imgsensor_value_t value);

/****************************************************************************
 * Private Data
 ****************************************************************************/

#ifndef ISX012_NOT_USE_NSTBY
static const isx012_reg_t g_isx012_presleep[] =
{
  {PLL_CKSEL, 0x00, 0x01}, /* PLL_CKSEL */
  {SRCCK_DIV, 0x00, 0x01}, /* SRCCK_DIV */
  {INCK_SET,  0x17, 0x01}, /* INCK_SET */
};
#define ISX012_PRESLEEP_NENTRIES ARRAY_NENTRIES(g_isx012_presleep)
#endif

static const isx012_reg_t g_isx012_def_init[] =
{
#ifdef ISX012_NOT_USE_NSTBY
  {PLL_CKSEL,         0x00, 0x01},
  {SRCCK_DIV,         0x00, 0x01},
#endif
  {DRIVABILITY,       0xaa, 0x01},
  {VIFCONFIG,       0x0200, 0x02},
  {YUVCONFIG_TN,    0xff0a, 0x02},
  {ILCODELEN,         0x00, 0x01},
  {AFMODE_MONI,       0x01, 0x01},
  {YUVCONFIG,       0xff6a, 0x02},
  {VIF_REC601_Y,    0x10fe, 0x02},
  {VIF_REC601_C,    0x10f0, 0x02},
  {HSENS_MODE_SEL,    0x11, 0x01},
  {VIF_CLKCONFIG1,    0x30, 0x01},
  {VIF_CLKCONFIG2,    0x30, 0x01},
  {VIF_CLKCONFIG3,    0x30, 0x01},
  {VIF_CLKCONFIG4,    0x30, 0x01},
  {VIF_CLKCONFIG5,    0x30, 0x01},
  {VIF_CLKCONFIG6,    0x30, 0x01},
  {VIF_CLKCONFIG7,    0x30, 0x01},
  {VIF_CLKCONFIG8,    0x30, 0x01},
  {VIF_CLKCONFIG9,    0x30, 0x01},
  {VIF_CLKCONFIG10,   0x30, 0x01},
  {VIF_CLKCONFIG11,   0x30, 0x01},
  {VIF_CLKCONFIG12,   0x30, 0x01},
  {VIF_CLKCONFIG13,   0x11, 0x01},
  {VIF_CLKCONFIG14,   0x11, 0x01},
  {VIF_CLKCONFIG15,   0x11, 0x01},
  {VIF_CLKCONFIG16,   0x11, 0x01},
#ifdef ISX012_NOT_USE_NSTBY
  {INCK_SET,          0x17, 0x01}, /* INCK_SET */
#endif
  {FRM_FIX_SN1_2,     0xff, 0x01}, /* Fix framerate */
  {FAST_MODECHG_EN,   0x01, 0x01},
  {FAST_SHT_MODE_SEL, 0x01, 0x01},
  {CAP_HALF_AE_CTRL,  0x07, 0x01}, /* HAFREL=HIGHSPEED, CAP=Auto  */
  {HALF_AWB_CTRL,     0x01, 0x01},
  {AESPEED_FAST,      0x0f, 0x01},
  {FASTMOVE_TIMEOUT,  0x2d, 0x01},
  {YGAMMA_MODE,       0x01, 0x01},
  {INT_QLTY2,         0x50, 0x01},
  {JPEG_PRED_MODE,    0x00, 0x01},
};

#define ISX012_RESET_NENTRIES ARRAY_NENTRIES(g_isx012_def_init)

static const uint8_t g_isx012_cxc_rgb_data[CXC_RGB_DATA_UNIT_NUM]
                                          [CXC_RGB_DATA_UNIT_SIZE] =
{
  {0x01, 0x43, 0xc0, 0xf0, 0x4f, 0xfc, 0x13},  /* CXC_RGB_UNIT0  */
  {0x80, 0x44, 0x20, 0x21, 0x48, 0x04, 0x0e},  /* CXC_RGB_UNIT1  */
  {0x81, 0x43, 0xc0, 0x10, 0x30, 0xfc, 0x13},  /* CXC_RGB_UNIT2  */
  {0xff, 0x04, 0x20, 0x11, 0x48, 0x08, 0x12},  /* CXC_RGB_UNIT3  */
  {0x81, 0x43, 0xe0, 0x20, 0x48, 0x08, 0x12},  /* CXC_RGB_UNIT4  */
  {0x80, 0x03, 0xe0, 0x00, 0x38, 0x04, 0x10},  /* CXC_RGB_UNIT5  */
  {0x01, 0x84, 0x20, 0x21, 0x48, 0x04, 0x10},  /* CXC_RGB_UNIT6  */
  {0x01, 0x04, 0xc0, 0x10, 0x20, 0x00, 0x08},  /* CXC_RGB_UNIT7  */
  {0x81, 0x82, 0xc0, 0x20, 0x38, 0x08, 0x0e},  /* CXC_RGB_UNIT8  */
  {0x01, 0x43, 0xc0, 0x10, 0x20, 0x04, 0x04},  /* CXC_RGB_UNIT9  */
  {0x01, 0x41, 0x40, 0x10, 0x20, 0x08, 0x0a},  /* CXC_RGB_UNIT10 */
  {0x82, 0x82, 0x80, 0x20, 0x20, 0x04, 0x04},  /* CXC_RGB_UNIT11 */
  {0x82, 0x80, 0x20, 0x20, 0x08, 0x04, 0x06},  /* CXC_RGB_UNIT12 */
  {0x81, 0x42, 0xa0, 0x10, 0x20, 0x04, 0x08},  /* CXC_RGB_UNIT13 */
  {0x81, 0x80, 0x00, 0x00, 0x00, 0x04, 0x00},  /* CXC_RGB_UNIT14 */
  {0x01, 0x41, 0x80, 0x10, 0x20, 0x00, 0x08},  /* CXC_RGB_UNIT15 */
  {0x00, 0x42, 0x20, 0x20, 0x08, 0x08, 0x00},  /* CXC_RGB_UNIT16 */
  {0x82, 0xc0, 0x40, 0x20, 0x20, 0x08, 0x08},  /* CXC_RGB_UNIT17 */
  {0x80, 0x02, 0xa0, 0x10, 0x20, 0x08, 0x04},  /* CXC_RGB_UNIT18 */
  {0x02, 0x81, 0x60, 0x30, 0x20, 0x08, 0x0a},  /* CXC_RGB_UNIT19 */
  {0x82, 0x42, 0xc0, 0x10, 0x30, 0x04, 0x0a},  /* CXC_RGB_UNIT20 */
  {0x03, 0xc3, 0xa0, 0x40, 0x28, 0x0c, 0x0a},  /* CXC_RGB_UNIT21 */
  {0x03, 0xc3, 0xc0, 0x20, 0x20, 0x08, 0x08},  /* CXC_RGB_UNIT22 */
  {0x82, 0xc2, 0xc0, 0x30, 0x40, 0x10, 0x0e},  /* CXC_RGB_UNIT23 */
  {0x84, 0x03, 0xa1, 0x40, 0x28, 0x08, 0x08},  /* CXC_RGB_UNIT24 */
  {0x02, 0x82, 0xa0, 0x30, 0x30, 0x0c, 0x10},  /* CXC_RGB_UNIT25 */
  {0x84, 0x03, 0xe1, 0x40, 0x28, 0x10, 0x0a},  /* CXC_RGB_UNIT26 */
};

static const uint8_t g_isx012_cxc_grb_data[CXC_GRB_DATA_UNIT_NUM]
                                          [CXC_GRB_DATA_UNIT_SIZE] =
{
  {0x00, 0x3d, 0x40, 0x0f, 0xc0, 0x03, 0xf2},  /* CXC_GRB_UNIT0  */
  {0x80, 0x7c, 0x80, 0x1f, 0xd8, 0x03, 0xf0},  /* CXC_GRB_UNIT1  */
  {0x00, 0x3c, 0x40, 0x0f, 0xd0, 0x03, 0xf0},  /* CXC_GRB_UNIT2  */
  {0x80, 0x3c, 0x20, 0x1f, 0xe0, 0x07, 0xf6},  /* CXC_GRB_UNIT3  */
  {0x00, 0x3c, 0x00, 0x1f, 0xd0, 0x07, 0xf4},  /* CXC_GRB_UNIT4  */
  {0x00, 0x3d, 0x40, 0x0f, 0xc8, 0x03, 0xf2},  /* CXC_GRB_UNIT5  */
  {0x80, 0xfc, 0x5f, 0xff, 0xd7, 0x07, 0xf4},  /* CXC_GRB_UNIT6  */
  {0x01, 0x7d, 0x40, 0x0f, 0xd0, 0xff, 0xf3},  /* CXC_GRB_UNIT7  */
  {0x7f, 0xfd, 0x3f, 0x0f, 0xc8, 0x03, 0xf2},  /* CXC_GRB_UNIT8  */
  {0x81, 0x7c, 0x20, 0x0f, 0xd0, 0xff, 0xf7},  /* CXC_GRB_UNIT9  */
  {0x7e, 0xfe, 0x5f, 0x0f, 0xd8, 0x03, 0xf6},  /* CXC_GRB_UNIT10 */
  {0x80, 0xbd, 0xa0, 0x2f, 0xe8, 0x07, 0xfa},  /* CXC_GRB_UNIT11 */
  {0x80, 0xfe, 0xbf, 0x0f, 0xe8, 0xff, 0xf9},  /* CXC_GRB_UNIT12 */
  {0x00, 0x3e, 0x80, 0x3f, 0xe8, 0x0f, 0xfa},  /* CXC_GRB_UNIT13 */
  {0x02, 0x40, 0xe0, 0x0f, 0xf8, 0x03, 0xfe},  /* CXC_GRB_UNIT14 */
  {0x80, 0x7f, 0xe0, 0x1f, 0xf8, 0x17, 0xfe},  /* CXC_GRB_UNIT15 */
  {0x85, 0xff, 0xe0, 0x2f, 0x08, 0x04, 0x04},  /* CXC_GRB_UNIT16 */
  {0x81, 0x40, 0x20, 0x20, 0x00, 0x08, 0x00},  /* CXC_GRB_UNIT17 */
  {0x84, 0x00, 0x21, 0x30, 0x10, 0x0c, 0x06},  /* CXC_GRB_UNIT18 */
  {0x02, 0x82, 0x40, 0x20, 0x10, 0x0c, 0x02},  /* CXC_GRB_UNIT19 */
  {0x83, 0x00, 0x21, 0x40, 0x08, 0x10, 0x06},  /* CXC_GRB_UNIT20 */
  {0x83, 0x82, 0xa0, 0x20, 0x20, 0x08, 0x08},  /* CXC_GRB_UNIT21 */
  {0x02, 0x81, 0x40, 0x30, 0x18, 0x0c, 0x06},  /* CXC_GRB_UNIT22 */
  {0x03, 0x81, 0x80, 0x10, 0x20, 0x04, 0x08},  /* CXC_GRB_UNIT23 */
  {0x82, 0x82, 0x80, 0x20, 0x20, 0x0c, 0x06},  /* CXC_GRB_UNIT24 */
  {0x83, 0xc1, 0x40, 0x20, 0x20, 0x04, 0x08},  /* CXC_GRB_UNIT25 */
  {0x01, 0x82, 0xa0, 0x20, 0x20, 0x08, 0x08},  /* CXC_GRB_UNIT26 */
};

static const uint8_t g_isx012_shd_rgb_data[SHD_RGB_DATA_UNIT_NUM]
                                          [SHD_RGB_DATA_UNIT_SIZE] =
{
  {0xf1, 0x59, 0x52, 0x7b, 0x98, 0xc4, 0x9d, 0x23, 0x29, 0x87, 0x46}, /* SHD_RGB_UNIT0  */
  {0xc6, 0x81, 0xd1, 0x70, 0x56, 0xe4, 0x9c, 0x1b, 0x6d, 0x07, 0x48}, /* SHD_RGB_UNIT1  */
  {0xdd, 0xf1, 0x51, 0x7d, 0xa8, 0xb4, 0x1e, 0x25, 0x49, 0xc7, 0x46}, /* SHD_RGB_UNIT2  */
  {0xbd, 0xf1, 0x50, 0x6d, 0x2a, 0x44, 0x1b, 0x0a, 0x01, 0x87, 0x44}, /* SHD_RGB_UNIT3  */
  {0xd0, 0xa9, 0x51, 0x77, 0x84, 0xd4, 0x9d, 0x1f, 0x2d, 0xc7, 0x44}, /* SHD_RGB_UNIT4  */
  {0xa8, 0xa9, 0xcf, 0x62, 0x98, 0xa3, 0x17, 0xdb, 0xfc, 0x05, 0x38}, /* SHD_RGB_UNIT5  */
  {0x90, 0xe1, 0x8e, 0x6a, 0x08, 0xc4, 0x9b, 0x0e, 0x11, 0x07, 0x43}, /* SHD_RGB_UNIT6  */
  {0xac, 0xa9, 0x4f, 0x5d, 0x4e, 0x13, 0x15, 0xb9, 0xf8, 0x44, 0x2b}, /* SHD_RGB_UNIT7  */
  {0x44, 0x21, 0xcb, 0x56, 0x0e, 0x63, 0x98, 0xe3, 0x78, 0x86, 0x3d}, /* SHD_RGB_UNIT8  */
  {0xab, 0x81, 0x4f, 0x62, 0x7c, 0xc3, 0x94, 0xb4, 0x98, 0x84, 0x26}, /* SHD_RGB_UNIT9  */
  {0x14, 0xe9, 0x48, 0x46, 0x4a, 0x12, 0x93, 0xa4, 0x84, 0xc5, 0x31}, /* SHD_RGB_UNIT10 */
  {0x81, 0xe9, 0x4d, 0x67, 0xac, 0x73, 0x17, 0xd0, 0xdc, 0x24, 0x29}, /* SHD_RGB_UNIT11 */
  {0x12, 0xb9, 0x08, 0x40, 0x02, 0x52, 0x10, 0x84, 0x6c, 0x64, 0x25}, /* SHD_RGB_UNIT12 */
  {0x4c, 0x91, 0xcb, 0x5b, 0x4c, 0xe3, 0x19, 0xec, 0xdc, 0x05, 0x34}, /* SHD_RGB_UNIT13 */
  {0x37, 0x39, 0x8a, 0x44, 0x2a, 0x02, 0x10, 0x80, 0x14, 0xe4, 0x20}, /* SHD_RGB_UNIT14 */
  {0x1c, 0x51, 0x49, 0x53, 0xe4, 0x02, 0x17, 0xd3, 0xb8, 0xe6, 0x3d}, /* SHD_RGB_UNIT15 */
  {0x8b, 0xd9, 0x8d, 0x53, 0xc8, 0x72, 0x12, 0x98, 0x50, 0x24, 0x23}, /* SHD_RGB_UNIT16 */
  {0x19, 0x11, 0x89, 0x4c, 0x8c, 0x32, 0x16, 0xc7, 0x14, 0x06, 0x38}, /* SHD_RGB_UNIT17 */
  {0xca, 0xc1, 0x10, 0x6c, 0xe0, 0x83, 0x97, 0xd0, 0x4c, 0xa5, 0x2d}, /* SHD_RGB_UNIT18 */
  {0x3e, 0x99, 0x0a, 0x51, 0xbc, 0xc2, 0x15, 0xc2, 0x28, 0x26, 0x39}, /* SHD_RGB_UNIT19 */
  {0xa5, 0x89, 0x0f, 0x7b, 0x8c, 0x64, 0x9d, 0x14, 0xb9, 0x46, 0x3e}, /* SHD_RGB_UNIT20 */
  {0x8f, 0x41, 0xce, 0x5e, 0x5e, 0x03, 0x98, 0xdc, 0x50, 0xe6, 0x3a}, /* SHD_RGB_UNIT21 */
  {0xb4, 0x49, 0x90, 0x72, 0x50, 0x74, 0xa1, 0x3a, 0x05, 0x88, 0x4b}, /* SHD_RGB_UNIT22 */
  {0xe1, 0xd1, 0x91, 0x71, 0x38, 0xc4, 0x1b, 0x0a, 0xed, 0x86, 0x42}, /* SHD_RGB_UNIT23 */
  {0xcb, 0x49, 0xd1, 0x78, 0x86, 0x74, 0x9f, 0x2d, 0xb9, 0x88, 0x51}, /* SHD_RGB_UNIT24 */
  {0x11, 0x62, 0x93, 0x7c, 0x9c, 0x94, 0x1d, 0x1b, 0x41, 0x67, 0x46}, /* SHD_RGB_UNIT25 */
  {0xcf, 0x81, 0x91, 0x77, 0x82, 0x54, 0x9f, 0x2a, 0x21, 0xa8, 0x4d}, /* SHD_RGB_UNIT26 */
};

static const uint8_t g_isx012_shd_grb_data[SHD_GRB_DATA_UNIT_NUM]
                                          [SHD_GRB_DATA_UNIT_SIZE] =
{
  {0xe8, 0xa9, 0x0f, 0x78, 0xe4, 0x13, 0x9d, 0xf0, 0x04, 0xe7, 0x39}, /* SHD_GRB_UNIT0  */
  {0xbd, 0x51, 0x0e, 0x6f, 0x94, 0x63, 0x1c, 0xea, 0x4c, 0x27, 0x3c}, /* SHD_GRB_UNIT1  */
  {0xd7, 0x19, 0x4f, 0x7a, 0xf4, 0xd3, 0x1d, 0xf7, 0x20, 0xe7, 0x3a}, /* SHD_GRB_UNIT2  */
  {0xb6, 0x11, 0x0e, 0x6c, 0x76, 0x03, 0x9b, 0xdd, 0xf0, 0x06, 0x39}, /* SHD_GRB_UNIT3  */
  {0xc9, 0xc1, 0x8e, 0x75, 0xc8, 0xe3, 0x9c, 0xef, 0xf8, 0xa6, 0x39}, /* SHD_GRB_UNIT4  */
  {0xa0, 0x69, 0x0d, 0x62, 0x20, 0x93, 0x97, 0xbf, 0xf4, 0xa5, 0x30}, /* SHD_GRB_UNIT5  */
  {0x8c, 0xb1, 0x8c, 0x68, 0x60, 0x13, 0x9b, 0xe0, 0xcc, 0xa6, 0x38}, /* SHD_GRB_UNIT6  */
  {0x9f, 0x71, 0x0d, 0x5c, 0xf4, 0x12, 0x15, 0xab, 0x00, 0x65, 0x28}, /* SHD_GRB_UNIT7  */
  {0x44, 0x41, 0x0a, 0x56, 0xbc, 0x02, 0x98, 0xc4, 0x50, 0x26, 0x34}, /* SHD_GRB_UNIT8  */
  {0x9a, 0x59, 0x4d, 0x5f, 0x16, 0x83, 0x14, 0xa8, 0x9c, 0x64, 0x25}, /* SHD_GRB_UNIT9  */
  {0x15, 0xc1, 0xc8, 0x46, 0x38, 0x22, 0x13, 0x9a, 0x74, 0x65, 0x2c}, /* SHD_GRB_UNIT10 */
  {0x78, 0x11, 0x4c, 0x63, 0x36, 0xb3, 0x96, 0xbb, 0xcc, 0x44, 0x27}, /* SHD_GRB_UNIT11 */
  {0x11, 0xa1, 0x48, 0x40, 0x04, 0x72, 0x10, 0x83, 0x70, 0x84, 0x23}, /* SHD_GRB_UNIT12 */
  {0x4a, 0x69, 0xca, 0x59, 0xe0, 0xf2, 0x98, 0xcc, 0xb0, 0xa5, 0x2e}, /* SHD_GRB_UNIT13 */
  {0x33, 0xc1, 0x09, 0x44, 0x24, 0x02, 0x10, 0x80, 0x14, 0x84, 0x20}, /* SHD_GRB_UNIT14 */
  {0x1b, 0xd1, 0x48, 0x52, 0x98, 0x72, 0x96, 0xb7, 0x8c, 0x06, 0x35}, /* SHD_GRB_UNIT15 */
  {0x81, 0x39, 0xcc, 0x51, 0x96, 0x32, 0x92, 0x92, 0x48, 0x44, 0x22}, /* SHD_GRB_UNIT16 */
  {0x17, 0xb9, 0x48, 0x4b, 0x5e, 0xa2, 0x15, 0xb0, 0xd8, 0x45, 0x30}, /* SHD_GRB_UNIT17 */
  {0xc0, 0x19, 0xce, 0x69, 0x56, 0x23, 0x97, 0xba, 0x38, 0x05, 0x2a}, /* SHD_GRB_UNIT18 */
  {0x3b, 0xe1, 0x09, 0x50, 0x82, 0x42, 0x95, 0xac, 0xf8, 0x05, 0x31}, /* SHD_GRB_UNIT19 */
  {0x94, 0x19, 0x4d, 0x78, 0xca, 0xe3, 0x9c, 0xe8, 0xa8, 0xa6, 0x35}, /* SHD_GRB_UNIT20 */
  {0x8b, 0x71, 0xcc, 0x5d, 0xf8, 0xa2, 0x97, 0xc0, 0x24, 0xa6, 0x32}, /* SHD_GRB_UNIT21 */
  {0xa4, 0xb1, 0x8d, 0x6d, 0x96, 0xd3, 0xa0, 0x09, 0xe1, 0xa7, 0x3f}, /* SHD_GRB_UNIT22 */
  {0xde, 0x09, 0xcf, 0x70, 0x92, 0x73, 0x9b, 0xe0, 0xcc, 0x06, 0x38}, /* SHD_GRB_UNIT23 */
  {0xc0, 0x89, 0x4e, 0x74, 0xcc, 0x13, 0x1e, 0xfc, 0x84, 0x48, 0x45}, /* SHD_GRB_UNIT24 */
  {0x06, 0x7a, 0xd0, 0x7a, 0xe6, 0x33, 0x1d, 0xef, 0x24, 0x07, 0x3b}, /* SHD_GRB_UNIT25 */
  {0xc4, 0xb1, 0x0e, 0x74, 0xca, 0x33, 0x1e, 0xfc, 0xc4, 0x07, 0x41}, /* SHD_GRB_UNIT26 */
};

static const uint8_t g_isx012_shd_r1_data[SHD_R1_DATA_UNIT_NUM]
                                         [SHD_R1_DATA_UNIT_SIZE] =
{
  {0x10, 0x92, 0x10, 0x82, 0xf8, 0x43, 0x1f, 0xfb, 0xf0, 0xe7, 0x40}, /* SHD_R1_UNIT0   */
  {0x07, 0x92, 0xd0, 0x82, 0xec, 0x33, 0x9e, 0xed, 0x68, 0xe7, 0x3c}, /* SHD_R1_UNIT1   */
  {0xfa, 0x21, 0xd0, 0x7e, 0xce, 0xa3, 0x1b, 0xcd, 0x20, 0xe6, 0x31}, /* SHD_R1_UNIT2   */
  {0xa6, 0x69, 0xce, 0x78, 0xbc, 0xa3, 0x1b, 0xbe, 0x44, 0x25, 0x28}, /* SHD_R1_UNIT3   */
  {0x45, 0x19, 0xcb, 0x65, 0x78, 0xe3, 0x1b, 0xc8, 0x3c, 0xa5, 0x24}, /* SHD_R1_UNIT4   */
  {0x15, 0xc1, 0x48, 0x4d, 0xd6, 0x72, 0x99, 0xd3, 0xdc, 0x25, 0x27}, /* SHD_R1_UNIT5   */
  {0x11, 0x01, 0x08, 0x41, 0x42, 0x42, 0x15, 0xc1, 0xa4, 0x06, 0x2f}, /* SHD_R1_UNIT6   */
  {0x39, 0x89, 0x08, 0x40, 0x0a, 0x22, 0x12, 0xab, 0x0c, 0x26, 0x38}, /* SHD_R1_UNIT7   */
  {0x91, 0x71, 0x4a, 0x49, 0x2c, 0xa2, 0x11, 0x9c, 0xc4, 0xa5, 0x33}, /* SHD_R1_UNIT8   */
  {0xe2, 0xe1, 0x4d, 0x5f, 0xa4, 0x22, 0x94, 0xa3, 0xa0, 0x05, 0x34}, /* SHD_R1_UNIT9   */
  {0xc7, 0x41, 0x50, 0x7c, 0x7e, 0xd3, 0x19, 0xc5, 0x48, 0x86, 0x35}, /* SHD_R1_UNIT10  */
  {0xda, 0xa9, 0xcf, 0x8c, 0x42, 0x24, 0x20, 0xf5, 0x8c, 0x67, 0x3c}, /* SHD_R1_UNIT11  */
  {0xf6, 0x89, 0xd0, 0x88, 0x90, 0x34, 0x23, 0x0b, 0x15, 0xa8, 0x3f}, /* SHD_R1_UNIT12  */
  {0x00, 0x72, 0x10, 0x89, 0x68, 0x04, 0x69, 0x00, 0x00, 0x19, 0x26}, /* SHD_R1_UNIT13  */
};

static const uint8_t g_isx012_shd_r2_data[SHD_R2_DATA_UNIT_NUM]
                                         [SHD_R2_DATA_UNIT_SIZE] =
{
  {0x3a, 0xe2, 0x11, 0x8c, 0x42, 0x74, 0xa1, 0x0c, 0x89, 0x08, 0x46}, /* SHD_R2_UNIT0   */
  {0x30, 0xe2, 0xd1, 0x8c, 0x36, 0x54, 0x20, 0xfe, 0xec, 0x47, 0x41}, /* SHD_R2_UNIT1   */
  {0x20, 0x5a, 0x91, 0x88, 0x16, 0x94, 0x1d, 0xda, 0x80, 0x26, 0x35}, /* SHD_R2_UNIT2   */
  {0xc2, 0x69, 0x0f, 0x81, 0x00, 0x94, 0x9d, 0xc9, 0x84, 0xe5, 0x29}, /* SHD_R2_UNIT3   */
  {0x54, 0xb1, 0x0b, 0x6c, 0xb2, 0xb3, 0x9d, 0xd4, 0x74, 0x85, 0x25}, /* SHD_R2_UNIT4   */
  {0x1a, 0xf1, 0x08, 0x50, 0xfc, 0xe2, 0x1a, 0xe0, 0x2c, 0x66, 0x28}, /* SHD_R2_UNIT5   */
  {0x14, 0x01, 0x88, 0x41, 0x4e, 0x32, 0x16, 0xcb, 0x08, 0x87, 0x31}, /* SHD_R2_UNIT6   */
  {0x42, 0x99, 0x08, 0x40, 0x0c, 0x72, 0x92, 0xb1, 0x58, 0x86, 0x3b}, /* SHD_R2_UNIT7   */
  {0xa8, 0xd9, 0xca, 0x4a, 0x32, 0xe2, 0x91, 0xa0, 0x04, 0x66, 0x36}, /* SHD_R2_UNIT8   */
  {0x02, 0xc2, 0x4e, 0x64, 0xbe, 0xd2, 0x94, 0xa9, 0xe0, 0xc5, 0x36}, /* SHD_R2_UNIT9   */
  {0xe1, 0x61, 0x91, 0x84, 0xb6, 0x43, 0x9b, 0xcf, 0x9c, 0x66, 0x38}, /* SHD_R2_UNIT10  */
  {0xf6, 0xa1, 0x50, 0x97, 0x8e, 0x34, 0x22, 0x04, 0x01, 0x08, 0x40}, /* SHD_R2_UNIT11  */
  {0x15, 0x9a, 0x51, 0x92, 0xf2, 0xd4, 0xa5, 0x1d, 0x99, 0xa8, 0x43}, /* SHD_R2_UNIT12  */
  {0x21, 0x82, 0x91, 0x92, 0xbe, 0xf4, 0x9e, 0xf3, 0x4c, 0x87, 0x38}, /* SHD_R2_UNIT13  */
};

static const uint8_t g_isx012_shd_b2_data[SHD_B2_DATA_UNIT_NUM]
                                         [SHD_B2_DATA_UNIT_SIZE] =
{
  {0xef, 0x39, 0xcf, 0x74, 0x88, 0xb3, 0x1b, 0xdf, 0x20, 0x47, 0x3b}, /* SHD_B2_UNIT0   */
  {0xdf, 0x59, 0xcf, 0x77, 0x8c, 0x43, 0x1b, 0xd7, 0xb8, 0x46, 0x37}, /* SHD_B2_UNIT1   */
  {0xcc, 0xc1, 0x0e, 0x73, 0x78, 0xa3, 0x99, 0xc1, 0xd0, 0x25, 0x2f}, /* SHD_B2_UNIT2   */
  {0x87, 0x09, 0x0d, 0x6c, 0x64, 0x93, 0x99, 0xb6, 0x30, 0xc5, 0x27}, /* SHD_B2_UNIT3   */
  {0x3f, 0xb1, 0x0a, 0x5f, 0x2a, 0x93, 0x99, 0xbc, 0x1c, 0x85, 0x24}, /* SHD_B2_UNIT4   */
  {0x16, 0xc9, 0x48, 0x4c, 0xb6, 0x92, 0x17, 0xc4, 0x94, 0x85, 0x26}, /* SHD_B2_UNIT5   */
  {0x10, 0x09, 0x88, 0x41, 0x3a, 0x52, 0x94, 0xb2, 0x2c, 0xc6, 0x2c}, /* SHD_B2_UNIT6   */
  {0x33, 0x79, 0x08, 0x40, 0x08, 0xc2, 0x11, 0xa2, 0x94, 0x65, 0x34}, /* SHD_B2_UNIT7   */
  {0x7e, 0x39, 0x4a, 0x48, 0x26, 0x52, 0x91, 0x96, 0x64, 0x05, 0x2f}, /* SHD_B2_UNIT8   */
  {0xbf, 0x09, 0x8d, 0x5b, 0x92, 0xa2, 0x93, 0x9d, 0x4c, 0x65, 0x2f}, /* SHD_B2_UNIT9   */
  {0x95, 0xf9, 0x0e, 0x73, 0x48, 0x63, 0x98, 0xb9, 0xd8, 0xa5, 0x30}, /* SHD_B2_UNIT10  */
  {0xa5, 0xb1, 0x8d, 0x83, 0xf4, 0xa3, 0x1d, 0xe0, 0xd0, 0x06, 0x36}, /* SHD_B2_UNIT11  */
  {0xbe, 0xa9, 0x4e, 0x79, 0x50, 0xd4, 0x20, 0xf6, 0x54, 0xa7, 0x38}, /* SHD_B2_UNIT12  */
  {0xc5, 0x91, 0xce, 0x7a, 0xf4, 0x03, 0x44, 0x00, 0x60, 0x60, 0x00}, /* SHD_B2_UNIT13  */
};

static const isx012_reg_t g_isx012_shd_thresholds[] =
{
  {SHD_INP_TH_HB_H_R2, 0x1478, 2},
  {SHD_INP_TH_HB_L_R2, 0x1380, 2},
  {SHD_INP_TH_LB_H_R2, 0x10cc, 2},
  {SHD_INP_TH_LB_L_R2, 0x1004, 2},
  {SHD_INP_TH_HB_H_RB, 0x10cc, 2},
  {SHD_INP_TH_HB_L_RB, 0x1004, 2},
  {SHD_INP_TH_LB_H_RB, 0x0000, 2},
  {SHD_INP_TH_LB_L_RB, 0x0000, 2},
};

#define ISX012_SHD_THRESHOLDS_NENTRIES ARRAY_NENTRIES(g_isx012_shd_thresholds)

static const isx012_reg_t g_isx012_shd_wb[] =
{
  {NORMR,              0x1101, 2},
  {NORMB,              0x0f7b, 2},
  {AWBPRER,            0x0147, 2},
  {AWBPREB,            0x022a, 2},
  {SHD_PRER_OFFSET_R2, 0x001b, 2},
  {SHD_PRER_OFFSET_RB, 0x000b, 2},
  {SHD_PREB_OFFSET_RB, 0x0003, 2},
};

#define ISX012_SHD_WB_NENTRIES ARRAY_NENTRIES(g_isx012_shd_wb)

static const int32_t g_isx012_colorfx_actual[] =
{
  IMGSENSOR_COLORFX_NONE,
  IMGSENSOR_COLORFX_BW,
  IMGSENSOR_COLORFX_SEPIA,
  IMGSENSOR_COLORFX_NEGATIVE,
  IMGSENSOR_COLORFX_SKETCH,
  IMGSENSOR_COLORFX_SOLARIZATION,
  IMGSENSOR_COLORFX_PASTEL
};

static const uint8_t g_isx012_colorfx_regval[] =
{
  REGVAL_EFFECT_NONE,
  REGVAL_EFFECT_MONOTONE,
  REGVAL_EFFECT_SEPIA,
  REGVAL_EFFECT_NEGPOS,
  REGVAL_EFFECT_SKETCH,
  REGVAL_EFFECT_SOLARIZATION,
  REGVAL_EFFECT_PASTEL
};

static const int32_t g_isx012_presetwb_actual[] =
{
  IMGSENSOR_WHITE_BALANCE_AUTO,
  IMGSENSOR_WHITE_BALANCE_INCANDESCENT,
  IMGSENSOR_WHITE_BALANCE_FLUORESCENT,
  IMGSENSOR_WHITE_BALANCE_DAYLIGHT,
  IMGSENSOR_WHITE_BALANCE_CLOUDY,
  IMGSENSOR_WHITE_BALANCE_SHADE
};

static const uint8_t g_isx012_presetwb_regval[] =
{
  REGVAL_AWB_ATM,
  REGVAL_AWB_LIGHTBULB,
  REGVAL_AWB_FLUORESCENTLIGHT,
  REGVAL_AWB_CLEARWEATHER,
  REGVAL_AWB_CLOUDYWEATHER,
  REGVAL_AWB_SHADE
};

static const int32_t g_isx012_photometry_actual[] =
{
  IMGSENSOR_EXPOSURE_METERING_AVERAGE,
  IMGSENSOR_EXPOSURE_METERING_CENTER_WEIGHTED,
  IMGSENSOR_EXPOSURE_METERING_SPOT,
  IMGSENSOR_EXPOSURE_METERING_MATRIX
};

static const uint8_t g_isx012_photometry_regval[] =
{
  REGVAL_PHOTOMETRY_AVERAGE,
  REGVAL_PHOTOMETRY_CENTERWEIGHT,
  REGVAL_PHOTOMETRY_SPOT,
  REGVAL_PHOTOMETRY_MULTIPATTERN
};

static const int32_t g_isx012_iso_actual[] =
{
  25 * 1000,
  32 * 1000,
  40 * 1000,
  50 * 1000,
  64 * 1000,
  80 * 1000,
  100 * 1000,
  125 * 1000,
  160 * 1000,
  200 * 1000,
  250 * 1000,
  320 * 1000,
  400 * 1000,
  500 * 1000,
  640 * 1000,
  800 * 1000,
  1000 * 1000,
  1250 * 1000,
  1600 * 1000
};

static const uint8_t g_isx012_iso_regval[] =
{
  REGVAL_ISO_25,
  REGVAL_ISO_32,
  REGVAL_ISO_40,
  REGVAL_ISO_50,
  REGVAL_ISO_64,
  REGVAL_ISO_80,
  REGVAL_ISO_100,
  REGVAL_ISO_125,
  REGVAL_ISO_160,
  REGVAL_ISO_200,
  REGVAL_ISO_250,
  REGVAL_ISO_320,
  REGVAL_ISO_400,
  REGVAL_ISO_500,
  REGVAL_ISO_640,
  REGVAL_ISO_800,
  REGVAL_ISO_1000,
  REGVAL_ISO_1250,
  REGVAL_ISO_1600
};

static const struct imgsensor_ops_s g_isx012_ops =
{
  isx012_is_available,                  /* is HW available */
  isx012_init,                          /* init */
  isx012_uninit,                        /* uninit */
  isx012_get_driver_name,               /* get driver name */
  isx012_validate_frame_setting,        /* validate_frame_setting */
  isx012_start_capture,                 /* start_capture */
  isx012_stop_capture,                  /* stop_capture */
  NULL,                                 /* get_frame_interval */
  isx012_get_supported_value,           /* get_supported_value */
  isx012_get_value,                     /* get_value */
  isx012_set_value                      /* set_value */
};

static isx012_dev_t g_isx012_private =
{
  {
    &g_isx012_ops,
  },
  NXMUTEX_INITIALIZER,
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static uint16_t isx012_getreg(FAR isx012_dev_t *priv,
                              uint16_t regaddr, uint16_t regsize)
{
  uint16_t regval = 0;
  uint8_t buffer[2];
  int ret;

  buffer[0] = regaddr >> 8;
  buffer[1] = regaddr & 0xff;

  nxmutex_lock(&priv->i2c_lock);

  /* Write the register address */

  ret = i2c_write(priv->i2c, &priv->i2c_cfg, (FAR uint8_t *)buffer, 2);
  if (ret < 0)
    {
      verr("i2c_write failed: %d\n", ret);
    }
  else
    {
      /* Restart and read 16bits from the register */

      ret = i2c_read(priv->i2c, &priv->i2c_cfg,
                     (FAR uint8_t *)buffer, regsize);
      if (ret < 0)
        {
          verr("i2c_read failed: %d\n", ret);
        }
    }

  nxmutex_unlock(&priv->i2c_lock);

  if (ret >= 0)
    {
      memcpy((FAR uint8_t *)&regval, (FAR uint8_t *)buffer, regsize);
    }

  return regval;
}

static int isx012_putreg(FAR isx012_dev_t *priv,
                         uint16_t regaddr, uint32_t regval, uint16_t regsize)
{
  uint8_t buffer[6];
  int ret;

  DEBUGASSERT(regsize <= 4);

  /* Set up for the transfer */

  buffer[0] = regaddr >> 8;   /* RegAddr Hi */
  buffer[1] = regaddr & 0xff; /* RegAddr Low */

  memcpy((FAR uint8_t *)&buffer[2], (FAR uint8_t *)&regval, regsize);

  nxmutex_lock(&priv->i2c_lock);

  /* And do it */

  ret = i2c_write(priv->i2c, &priv->i2c_cfg,
                 (FAR uint8_t *)buffer, regsize + 2);
  if (ret < 0)
    {
      verr("i2c_write failed: %d\n", ret);
    }

  nxmutex_unlock(&priv->i2c_lock);
  return ret;
}

static int isx012_putreglist(FAR isx012_dev_t *priv,
                             FAR const isx012_reg_t *reglist,
                             size_t nentries)
{
  FAR const isx012_reg_t *entry;
  int ret = OK;

  for (entry = reglist; nentries > 0; nentries--, entry++)
    {
      ret = isx012_putreg(priv, entry->regaddr,
                          entry->regval, entry->regsize);
      if (ret < 0)
        {
          verr("isx012_putreg failed: %d\n", ret);
          return ret;
        }
    }

  return ret;
}

static int isx012_chk_int_state(FAR isx012_dev_t *priv,
                                uint8_t sts, uint32_t delay_time,
                                uint32_t wait_time, uint32_t timeout)
{
  int ret = 0;
  uint8_t data;
  uint32_t time = 0;

  nxsig_usleep(delay_time * 1000);
  while (time < timeout)
    {
      data = isx012_getreg(priv, INTSTS0, sizeof(data));
      data = data & sts;
      if (data != 0)
        {
          ret = isx012_putreg(priv, INTCLR0, data, sizeof(data));
          return ret;
        }

      nxsig_usleep(wait_time * 1000);
      time += wait_time;
    }

  return ERROR;
}

static int isx012_replace_fmt_to_regval(uint8_t nr_fmt,
                                        FAR imgsensor_format_t *fmt)
{
  int ret;

  if (fmt == NULL)
    {
      return -EINVAL;
    }

  switch (fmt[IMGSENSOR_FMT_MAIN].pixelformat)
    {
      case IMGSENSOR_PIX_FMT_UYVY:
        ret = REGVAL_OUTFMT_YUV;
        break;

      case IMGSENSOR_PIX_FMT_RGB565:
        ret = REGVAL_OUTFMT_RGB;
        break;

      case IMGSENSOR_PIX_FMT_JPEG:
        ret = REGVAL_OUTFMT_JPEG;
        break;

      case IMGSENSOR_PIX_FMT_JPEG_WITH_SUBIMG:
        if (nr_fmt == 1)
          {
            ret = REGVAL_OUTFMT_JPEG;
          }
        else
          {
            ret = REGVAL_OUTFMT_INTERLEAVE;
          }

        break;

      default:  /* Unsupported format */

        ret = -EINVAL;
    }

  return ret;
}

static bool is_movie_needed(uint8_t fmt, uint8_t fps)
{
  bool need = true;

  if ((fmt == IMGSENSOR_PIX_FMT_UYVY) ||
      (fmt == IMGSENSOR_PIX_FMT_RGB565))
    {
      if (fps >= REGVAL_FPSTYPE_30FPS)   /* This means fps <= 30 */
        {
          need = false;
        }
    }

  return need;
}

static void resize_for_clip(uint8_t nr_fmt,
                            FAR imgsensor_format_t *fmt,
                            FAR isx012_rect_t *clip,
                            FAR uint16_t *w,
                            FAR uint16_t *h,
                            FAR uint16_t *s_w,
                            FAR uint16_t *s_h)
{
  ASSERT(fmt && clip && w && h && s_w && s_h);

  *w = (clip->width  == 0) ? fmt[IMGSENSOR_FMT_MAIN].width  : clip->width;
  *h = (clip->height == 0) ? fmt[IMGSENSOR_FMT_MAIN].height : clip->height;

  if (nr_fmt > 1)
    {
      *s_w = fmt[IMGSENSOR_FMT_SUB].width;
      if (clip->width > 0)
        {
          *s_w = (uint32_t)*s_w * clip->width /
                 fmt[IMGSENSOR_FMT_MAIN].width;
        }

      *s_h = fmt[IMGSENSOR_FMT_SUB].height;
      if (clip->height > 0)
        {
          *s_h = (uint32_t)*s_h * clip->height /
                 fmt[IMGSENSOR_FMT_MAIN].height;
        }
    }
}

static void calc_clip_regval(uint16_t pos,
                             uint16_t clip_sz,
                             uint16_t frm_sz,
                             uint16_t basis_sz,
                             FAR uint32_t *ratio,
                             FAR int32_t  *offset)
{
  DEBUGASSERT(ratio && offset);

  *ratio = ZOOM_UNIT;
  *offset = 0;

  if (clip_sz != 0)
    {
      /* Clip by setting zoom up. */

      *ratio *= frm_sz;
      *ratio /= clip_sz;

      /* Applications' request pos means position from the upper left corner.
       * On the other hand, ISX012's register means the center of the image,
       * which has the maximum size of the sensor.
       */

      *offset = CLIP_OFFSET_UNIT;
      *offset *= (int16_t)(pos + (clip_sz / 2) - (frm_sz / 2));
      *offset *= basis_sz;
      *offset /= (int32_t)frm_sz;
    }
}

static bool is_clipped(FAR isx012_rect_t *clip)
{
  DEBUGASSERT(clip);

  if ((clip->left   == 0) &&
      (clip->top    == 0) &&
      (clip->width  == 0) &&
      (clip->height == 0))
    {
      return false;
    }

  return true;
}

static void activate_clip(FAR isx012_dev_t *priv,
                          uint16_t w,
                          uint16_t h,
                          FAR isx012_rect_t *clip)
{
  uint8_t  hvfree = 0;
  uint32_t r_x    = ZOOM_UNIT;
  uint32_t r_y    = ZOOM_UNIT;
  int32_t  x      = 0;
  int32_t  y      = 0;

  DEBUGASSERT(priv && clip);

  if (is_clipped(clip))
    {
      hvfree = 1;

      calc_clip_regval
        (clip->left, clip->width,  w, BASE_HSIZE_FOR_CLIP_OFFSET, &r_x, &x);
      calc_clip_regval
        (clip->top,  clip->height, h, BASE_VSIZE_FOR_CLIP_OFFSET, &r_y, &y);

      if (w * 3 > h * 4)
        {
          /* In case that aspect ratio is longer horizontally than 4:3,
           * re-scaling vertical component setting.
           */

          r_y = RESCALE_FOR_CLIP(r_y, w * 3, h * 4);
          y   = RESCALE_FOR_CLIP(y,   h * 4, w * 3);
        }
      else if (w * 3 < h * 4)
        {
          /* In case that aspect ratio is longer vertically than 4:3,
           * re-scaling horizontal component setting.
           */

          r_x = RESCALE_FOR_CLIP(r_x, h * 4, w * 3);
          x   = RESCALE_FOR_CLIP(x,   w * 3, h * 4);
        }
    }

  isx012_putreg(priv, HVFREEZOOM, hvfree, 1);
  isx012_putreg(priv, EZOOM_MAG,  ZOOM_UNIT,     sizeof(uint16_t));
  isx012_putreg(priv, EZOOM_HMAG, (uint16_t)r_x, sizeof(uint16_t));
  isx012_putreg(priv, EZOOM_VMAG, (uint16_t)r_y, sizeof(uint16_t));
  isx012_putreg(priv, OFFSET_X, (int16_t)x, sizeof(int16_t));
  isx012_putreg(priv, OFFSET_Y, (int16_t)y, sizeof(int16_t));
}

static int isx012_set_mode_param(FAR isx012_dev_t *priv,
                                 imgsensor_stream_type_t type,
                                 uint8_t nr_fmt,
                                 FAR imgsensor_format_t *fmt,
                                 FAR imgsensor_interval_t *interval)
{
  int ret = 0;
  int fmt_val = isx012_replace_fmt_to_regval(nr_fmt, fmt);
  int fps_val = isx012_replace_frameinterval_to_regval(interval);
  uint16_t fps_addr;
  uint16_t fmt_addr;
  uint16_t smode_addr;
  uint16_t hsize_addr;
  uint16_t vsize_addr;
  uint8_t smode;
  uint8_t mode;
  FAR isx012_rect_t *clip;
  uint16_t w = 0;
  uint16_t h = 0;
  uint16_t s_w = 0;
  uint16_t s_h = 0;

  /* Get register address for type  */

  if (type == IMGSENSOR_STREAM_TYPE_VIDEO)
    {
      if (is_movie_needed(fmt_val, fps_val))
        {
          if (priv->mode == REGVAL_MODESEL_HREL)
            {
              /* In Half release state,
               * the setting which need movie mode is prohibited.
               */

              return -EPERM;
            }

          fps_addr   = FPSTYPE_MOVIE;
          fmt_addr   = OUTFMT_MOVIE;
          smode_addr = SENSMODE_MOVIE;
          hsize_addr = HSIZE_MOVIE;
          vsize_addr = VSIZE_MOVIE;
          mode       = REGVAL_MODESEL_MOV;
        }
      else
        {
          fps_addr   = FPSTYPE_MONI;
          fmt_addr   = OUTFMT_MONI;
          smode_addr = SENSMODE_MONI;
          hsize_addr = HSIZE_MONI;
          vsize_addr = VSIZE_MONI;
          mode       = REGVAL_MODESEL_MON;
        }

      clip = &priv->clip_video;
    }
  else
    {
      fps_addr   = FPSTYPE_CAP;
      fmt_addr   = OUTFMT_CAP;
      smode_addr = SENSMODE_CAP;
      hsize_addr = HSIZE_CAP;
      vsize_addr = VSIZE_CAP;
      mode       = REGVAL_MODESEL_CAP;

      clip = &priv->clip_still;
    }

  ret = isx012_putreg(priv, fps_addr, fps_val, sizeof(uint8_t));
  if (ret < 0)
    {
      return ret;
    }

  ret = isx012_putreg(priv, fmt_addr, fmt_val, sizeof(uint8_t));
  if (ret < 0)
    {
      return ret;
    }

  switch (fps_val)
    {
      case REGVAL_FPSTYPE_120FPS:
        smode = REGVAL_SENSMODE_1_8;
        break;

      case REGVAL_FPSTYPE_60FPS:
        smode = REGVAL_SENSMODE_1_4;
        break;

      case REGVAL_FPSTYPE_30FPS:
        smode = REGVAL_SENSMODE_1_2;
        break;

      default:
        smode = REGVAL_SENSMODE_ALLPIX;
        break;
    }

  ret = isx012_putreg(priv, smode_addr, smode, sizeof(uint8_t));
  if (ret < 0)
    {
      return ret;
    }

  resize_for_clip(nr_fmt, fmt, clip, &w, &h, &s_w, &s_h);
  activate_clip(priv,
                fmt[IMGSENSOR_FMT_MAIN].width,
                fmt[IMGSENSOR_FMT_MAIN].height,
                clip);

  ret = isx012_putreg(priv, hsize_addr, w, sizeof(uint16_t));
  if (ret < 0)
    {
      return ret;
    }

  ret = isx012_putreg(priv, vsize_addr, h, sizeof(uint16_t));
  if (ret < 0)
    {
      return ret;
    }

  if (fmt_val == REGVAL_OUTFMT_INTERLEAVE)
    {
      ret = isx012_putreg(priv, HSIZE_TN, s_w, sizeof(uint16_t));
      if (ret < 0)
        {
          return ret;
        }

      ret = isx012_putreg(priv, VSIZE_TN, s_h, sizeof(uint16_t));
      if (ret < 0)
        {
          return ret;
        }
    }

  if (priv->state != STATE_ISX012_ACTIVE)
    {
      isx012_change_device_state(priv, STATE_ISX012_ACTIVE);
    }

  ret = isx012_change_camera_mode(priv, mode);
  if (ret == OK)
    {
      priv->mode = mode;
    }

  return ret;
}

/****************************************************************************
 * isx012_change_camera_mode
 ****************************************************************************/

static int isx012_change_camera_mode(FAR isx012_dev_t *priv, uint8_t mode)
{
  int      ret = 0;
  uint16_t fmt_addr;
  uint8_t  fmt;
  uint32_t vifmode;
#ifdef ISX012_FRAME_SKIP_EN
  uint8_t mask_num;
  int i;
#endif /* ISX012_FRAME_SKIP_EN */

  if (priv->state != STATE_ISX012_ACTIVE)
    {
      return -EPERM;
    }

  switch (mode)
    {
      case REGVAL_MODESEL_MON:
      case REGVAL_MODESEL_HREL:
        fmt_addr = OUTFMT_MONI;
        break;

      case REGVAL_MODESEL_MOV:
        fmt_addr = OUTFMT_MOVIE;
        break;

      case REGVAL_MODESEL_CAP:
        fmt_addr = OUTFMT_CAP;
        break;

      default:
        return -EPERM;
    }

  fmt = isx012_getreg(priv, fmt_addr, 1);

  switch (fmt) /* mode parallel */
    {
      case REGVAL_OUTFMT_YUV:
        vifmode = REGVAL_VIFMODE_YUV_PARALLEL;
        break;
      case REGVAL_OUTFMT_JPEG:
        vifmode = REGVAL_VIFMODE_JPEG_PARALLEL;
        break;
      case REGVAL_OUTFMT_INTERLEAVE:
        vifmode = REGVAL_VIFMODE_INTERLEAVE_PARALLEL;
        break;
      case REGVAL_OUTFMT_RGB:
        vifmode = REGVAL_VIFMODE_RGB_PARALLEL;
        break;
      default:
        vifmode = REGVAL_VIFMODE_YUV_PARALLEL;
        break;
    }

  ret = isx012_putreg(priv, VIFMODE, vifmode, sizeof(vifmode));
  if (ret < 0)
    {
      return ret;
    }

  if (mode != isx012_getreg(priv, MODESEL, sizeof(mode)))
    {
      isx012_putreg(priv, INTCLR0, CM_CHANGED_STS, 1);

      ret = isx012_putreg(priv, MODESEL, mode, sizeof(mode));
      if (ret < 0)
        {
          return ret;
        }

      /* Wait CM_CHANGED */

      ret = isx012_chk_int_state(priv,
                                 CM_CHANGED_STS,
                                 CAMERA_MODE_DELAY_TIME,
                                 CAMERA_MODE_WAIT_TIME,
                                 CAMERA_MODE_TIMEOUT);
      if (ret != 0)
        {
          return ret;
        }
    }

#ifdef ISX012_FRAME_SKIP_EN
  if (mode != REGVAL_MODESEL_HREL)
    {
      isx012_putreg(priv, INTCLR0, VINT_STS, 1);
      mask_num = isx012_getreg(priv, RO_MASK_NUM, sizeof(mask_num));
      for (i = 0; i < mask_num; i++)
        {
          /* Wait Next VINT */

          ret = isx012_chk_int_state(priv, VINT_STS, VINT_DELAY_TIME,
                                           VINT_WAIT_TIME, VINT_TIMEOUT);
          if (ret != 0)
            {
              return ret;
            }
        }
    }
#endif /* ISX012_FRAME_SKIP_EN */

  return OK;
}

/****************************************************************************
 * isx012_change_device_state
 ****************************************************************************/

static int isx012_change_device_state(FAR isx012_dev_t *priv,
                                      isx012_state_t state)
{
  int ret = 0;
#ifdef ISX012_FRAME_SKIP_EN
  int i;
  uint8_t mute_cnt;
#endif /* ISX012_FRAME_SKIP_EN */

  if (priv->state == STATE_ISX012_PRESLEEP || priv->state == state)
    {
      return -EPERM;
    }

  switch (state)
    {
      case STATE_ISX012_SLEEP:
        isx012_putreg(priv, INTCLR0, OM_CHANGED_STS, 1);
        board_isx012_set_sleep(1);
        break;
      case STATE_ISX012_ACTIVE:
        isx012_putreg(priv, INTCLR0, OM_CHANGED_STS | CM_CHANGED_STS, 1);
        board_isx012_release_sleep();
        break;
      case STATE_ISX012_PRESLEEP:
        return -EPERM;
      default:
        return -EPERM;
    }

  /* Wait OM_CHANGED */

  ret = isx012_chk_int_state(priv, OM_CHANGED_STS,
                                   DEVICE_STATE_DELAY_TIME,
                                   DEVICE_STATE_WAIT_TIME,
                                   DEVICE_STATE_TIMEOUT);
  if (ret != 0)
    {
      return ret;
    }

  priv->state = state;

  if (state == STATE_ISX012_ACTIVE)
    {
      /* Wait CM_CHANGED -> Monitoring */

      ret = isx012_chk_int_state(priv, CM_CHANGED_STS,
                                       CAMERA_MODE_DELAY_TIME,
                                       CAMERA_MODE_WAIT_TIME,
                                       CAMERA_MODE_TIMEOUT);
      if (ret != 0)
        {
          return ret;
        }

#ifdef ISX012_FRAME_SKIP_EN
      mute_cnt = isx012_getreg(priv, MUTECNT, sizeof(mute_cnt));
      isx012_putreg(priv, INTCLR0, VINT_STS, 1);
      for (i = 0; i < mute_cnt; i++)
        {
          /* Wait Next VINT */

          ret = isx012_chk_int_state(priv, VINT_STS, VINT_DELAY_TIME,
                                           VINT_WAIT_TIME, VINT_TIMEOUT);
          if (ret != 0)
            {
              return ret;
            }
        }
#endif  /* ISX012_FRAME_SKIP_EN */
    }

  priv->mode = REGVAL_MODESEL_MON;

  return OK;
}

int init_isx012(FAR isx012_dev_t *priv)
{
  int ret;

#ifdef ISX012_NOT_USE_NSTBY
  board_isx012_release_sleep();
  board_isx012_release_reset();
  nxsig_usleep(6000);
#else
  board_isx012_release_reset();
  nxsig_usleep(6000);
#endif

#ifdef ISX012_CHECK_IN_DETAIL
  /* check the chip id */

  ret = isx012_chipid(priv);
  if (ret < 0)
    {
      verr("isx012_chipid failed: %d\n", ret);
      board_isx012_set_reset();
      return ret;
    }
#endif

  /* Wait OM_CHANGED Power OFF -> PreSleep */

  ret = isx012_chk_int_state(priv, OM_CHANGED_STS, DEVICE_STATE_DELAY_TIME,
                             DEVICE_STATE_WAIT_TIME, DEVICE_STATE_TIMEOUT);
  if (ret != OK)
    {
      verr("OM_CHANGED_STS(PreSleep) is Not occurred: %d\n", ret);
      return ret;
    }

  priv->state = STATE_ISX012_PRESLEEP;

#ifndef ISX012_NOT_USE_NSTBY
  /* set the isx012 clock */

  /* Write INCK_SET register ISX012 change state PreSleep -> Sleep */

  ret = isx012_putreglist(priv, g_isx012_presleep, ISX012_PRESLEEP_NENTRIES);
  if (ret != OK)
    {
      verr("isx012_putreglist(INCK_SET) failed: %d\n", ret);
      return ret;
    }

  /* Wait OM_CHANGED PreSleep -> Sleep */

  ret = isx012_chk_int_state(priv, OM_CHANGED_STS, DEVICE_STATE_DELAY_TIME,
                             DEVICE_STATE_WAIT_TIME, DEVICE_STATE_TIMEOUT);
  if (ret != OK)
    {
      verr("OM_CHANGED_STS(Sleep) is Not occurred: %d\n", ret);
      return ret;
    }
#endif

  priv->state = STATE_ISX012_SLEEP;
  priv->i2c_cfg.frequency = I2CFREQ_FAST;

  /* initialize the isx012 hardware */

  ret = isx012_putreglist(priv, g_isx012_def_init, ISX012_RESET_NENTRIES);
  if (ret < 0)
    {
      verr("isx012_putreglist failed: %d\n", ret);
      board_isx012_set_reset();
      return ret;
    }

  /* Set shading adjustment */

  ret = isx012_set_shd(priv);
  if (ret < 0)
    {
      verr("isx012_set_shd failed: %d\n", ret);
      board_isx012_set_reset();
      return ret;
    }

  return ret;
}

static bool isx012_is_available(FAR struct imgsensor_s *sensor)
{
  FAR isx012_dev_t *priv = (FAR isx012_dev_t *)sensor;
  bool ret;

  isx012_init(sensor);

  /* Try to access via I2C.
   * Select DEVICESTS register, which has positive value.
   */

  ret = (isx012_getreg(priv, DEVICESTS, 1) == DEVICESTS_SLEEP);

  isx012_uninit(sensor);

  return ret;
}

static int isx012_init(FAR struct imgsensor_s *sensor)
{
  FAR isx012_dev_t *priv = (FAR isx012_dev_t *)sensor;
  int ret = 0;

  priv->i2c               = board_isx012_initialize();
  priv->i2c_cfg.address   = ISX012_I2C_SLV_ADDR;
  priv->i2c_cfg.addrlen   = 7;
  priv->i2c_cfg.frequency = I2CFREQ_STANDARD;

  ret = board_isx012_power_on();
  if (ret < 0)
    {
      verr("Failed to power on %d\n", ret);
      return ret;
    }

  ret = init_isx012(priv);
  if (ret < 0)
    {
      verr("Failed to init_isx012 %d\n", ret);
      board_isx012_set_reset();
      board_isx012_power_off();
      return ret;
    }

  return ret;
}

static int isx012_uninit(FAR struct imgsensor_s *sensor)
{
  FAR isx012_dev_t *priv = (FAR isx012_dev_t *)sensor;

  int ret = 0;

  if (priv->state == STATE_ISX012_ACTIVE)
    {
      board_isx012_set_sleep(1);
    }

  board_isx012_set_reset();

  ret = board_isx012_power_off();
  if (ret < 0)
    {
      verr("Failed to power off %d\n", ret);
      return ret;
    }

  board_isx012_uninitialize(priv->i2c);

  priv->i2c_cfg.frequency = I2CFREQ_STANDARD;
  priv->state             = STATE_ISX012_POWEROFF;

  return ret;
}

static FAR const char *isx012_get_driver_name(FAR struct imgsensor_s *sensor)
{
  return "ISX012";
}

static int8_t isx012_get_maximum_fps(uint8_t nr_fmt,
                                     FAR imgsensor_format_t *fmt)
{
  int8_t max_fps = REGVAL_FPSTYPE_120FPS;
  uint16_t main_w;
  uint16_t main_h;
  uint16_t sub_w;
  uint16_t sub_h;

  main_w = fmt[IMGSENSOR_FMT_MAIN].width;
  main_h = fmt[IMGSENSOR_FMT_MAIN].height;

  switch (fmt[IMGSENSOR_FMT_MAIN].pixelformat)
    {
      case IMGSENSOR_PIX_FMT_UYVY:                /* YUV 4:2:2 */
      case IMGSENSOR_PIX_FMT_RGB565:              /* RGB565 */

        if ((main_w < OUT_YUV_HSIZE_MIN) ||
            (main_h < OUT_YUV_VSIZE_MIN) ||
            (main_w > OUT_YUV_15FPS_HSIZE_MAX) ||
            (main_h > OUT_YUV_15FPS_VSIZE_MAX))
          {
            /* IN frame size is out of range */

            return -EINVAL;
          }
        else if ((main_w <= OUT_YUV_120FPS_HSIZE_MAX) &&
                 (main_h <= OUT_YUV_120FPS_VSIZE_MAX))
          {
            max_fps = REGVAL_FPSTYPE_120FPS;
          }
        else
          {
            max_fps = REGVAL_FPSTYPE_60FPS;
          }

        break;

      case IMGSENSOR_PIX_FMT_JPEG:                /* JPEG */

        if ((main_w < OUT_JPG_HSIZE_MIN) ||
            (main_h < OUT_JPG_VSIZE_MIN) ||
            (main_w > OUT_JPG_15FPS_HSIZE_MAX) ||
            (main_h > OUT_JPG_15FPS_VSIZE_MAX))
          {
            /* IN frame size is out of range */

            return -EINVAL;
          }
        else if ((main_w <= OUT_JPG_120FPS_HSIZE_MAX) &&
                 (main_h <= OUT_JPG_120FPS_VSIZE_MAX))
          {
            max_fps = REGVAL_FPSTYPE_120FPS;
          }
        else if ((main_w <= OUT_JPG_60FPS_HSIZE_MAX) &&
                 (main_h <= OUT_JPG_60FPS_VSIZE_MAX))
          {
            max_fps = REGVAL_FPSTYPE_60FPS;
          }
        else if ((main_w <= OUT_JPG_30FPS_HSIZE_MAX) &&
                 (main_h <= OUT_JPG_30FPS_VSIZE_MAX))
          {
            max_fps = REGVAL_FPSTYPE_30FPS;
          }
        else
          {
            max_fps = REGVAL_FPSTYPE_15FPS;
          }

        break;

      case IMGSENSOR_PIX_FMT_JPEG_WITH_SUBIMG: /* JPEG + sub image */

        if (nr_fmt == 1)
          {
            sub_w = OUT_YUV_HSIZE_MIN;
            sub_h = OUT_YUV_VSIZE_MIN;
          }
        else
          {
            if (fmt[IMGSENSOR_FMT_SUB].pixelformat
                != IMGSENSOR_PIX_FMT_UYVY)
              {
                /* Unsupported pixel format */

                return -EINVAL;
              }

            sub_w  = fmt[IMGSENSOR_FMT_SUB].width;
            sub_h  = fmt[IMGSENSOR_FMT_SUB].height;
          }

        if ((main_w < OUT_JPG_HSIZE_MIN) ||
            (main_h < OUT_JPG_VSIZE_MIN) ||
            (main_w > OUT_JPGINT_15FPS_HSIZE_MAX) ||
            (main_h > OUT_JPGINT_15FPS_VSIZE_MAX) ||
            (sub_w  < OUT_YUV_HSIZE_MIN) ||
            (sub_h  < OUT_YUV_VSIZE_MIN) ||
            (sub_w  > OUT_YUVINT_30FPS_HSIZE_MAX) ||
            (sub_h  > OUT_YUVINT_30FPS_VSIZE_MAX))
          {
            /* IN frame size is out of range */

            return -EINVAL;
          }
        else if ((main_w <= OUT_JPGINT_30FPS_HSIZE_MAX) &&
                 (main_h <= OUT_JPGINT_30FPS_VSIZE_MAX))
          {
            max_fps = REGVAL_FPSTYPE_30FPS;
          }
        else
          {
            max_fps = REGVAL_FPSTYPE_15FPS;
          }

        break;

      default:
        return -EINVAL;
    }

  return max_fps;
}

static int isx012_replace_frameinterval_to_regval
           (FAR imgsensor_interval_t *interval)
{
  /* Avoid multiplication overflow */

  if ((interval->denominator * 2) / 2 != interval->denominator)
    {
      return -EINVAL;
    }

  /* Avoid division by zero */

  if (interval->numerator == 0)
    {
      return -EINVAL;
    }

  /* Support only 1/x or 2/x. */

  if (((interval->denominator * 2) % interval->numerator) != 0)
    {
      return -EINVAL;
    }

  /* Switch by FPS * 2 */

  switch ((interval->denominator * 2) / interval->numerator)
    {
      case 240 : /* 120FPS */
        return REGVAL_FPSTYPE_120FPS;

      case 120 : /* 60FPS */
        return REGVAL_FPSTYPE_60FPS;

      case 60 :  /* 30FPS */
        return REGVAL_FPSTYPE_30FPS;

      case 30 :  /* 15FPS */
        return REGVAL_FPSTYPE_15FPS;

      case 20 :  /* 10FPS */
        return REGVAL_FPSTYPE_10FPS;

      case 15 :  /* 7.5FPS */
        return REGVAL_FPSTYPE_7_5FPS;

      case 12 :  /* 6FPS */
        return REGVAL_FPSTYPE_6FPS;

      case 10 :  /* 5FPS */
        return REGVAL_FPSTYPE_5FPS;

      default :
        return -EINVAL;
    }
}

static int isx012_validate_frame_setting(FAR struct imgsensor_s *sensor,
                                         imgsensor_stream_type_t type,
                                         uint8_t nr_fmt,
                                         FAR imgsensor_format_t *fmt,
                                         FAR imgsensor_interval_t *interval)
{
  int max_fps;
  int arg_fps;

  if ((fmt == NULL) ||
      (interval == NULL))
    {
      return -EINVAL;
    }

  if ((nr_fmt < 1) || (nr_fmt > 2))
    {
      return -EINVAL;
    }

  max_fps = isx012_get_maximum_fps(nr_fmt, fmt);
  if (max_fps == -EINVAL)
    {
      return -EINVAL;
    }

  arg_fps = isx012_replace_frameinterval_to_regval(interval);
  if (arg_fps == -EINVAL)
    {
      return -EINVAL;
    }

  if (max_fps > arg_fps)
    {
      return -EINVAL;
    }

  return OK;
}

static int isx012_start_capture(FAR struct imgsensor_s *sensor,
                                imgsensor_stream_type_t type,
                                uint8_t nr_fmt,
                                FAR imgsensor_format_t *fmt,
                                FAR imgsensor_interval_t *interval)
{
  FAR isx012_dev_t *priv = (FAR isx012_dev_t *)sensor;
  int ret;

  ret = isx012_validate_frame_setting(sensor, type, nr_fmt, fmt, interval);
  if (ret != OK)
    {
      return ret;
    }

  return isx012_set_mode_param(priv, type, nr_fmt, fmt, interval);
}

static int isx012_stop_capture(FAR struct imgsensor_s *sensor,
                               imgsensor_stream_type_t type)
{
  return OK;
}

static int isx012_get_supported_value(FAR struct imgsensor_s *sensor,
             uint32_t id, FAR imgsensor_supported_value_t *value)
{
  int ret = OK;
  FAR imgsensor_capability_range_t *range = &value->u.range;
  FAR imgsensor_capability_discrete_t *discrete = &value->u.discrete;
  FAR imgsensor_capability_elems_t *elems = &value->u.elems;

  ASSERT(value);

  switch (id)
    {
      case IMGSENSOR_ID_BRIGHTNESS:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_BRIGHTNESS;
        range->maximum       = ISX012_MAX_BRIGHTNESS;
        range->step          = ISX012_STEP_BRIGHTNESS;
        range->default_value = ISX012_DEF_BRIGHTNESS;

        break;

      case IMGSENSOR_ID_CONTRAST:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_CONTRAST;
        range->maximum       = ISX012_MAX_CONTRAST;
        range->step          = ISX012_STEP_CONTRAST;
        range->default_value = ISX012_DEF_CONTRAST;

        break;

      case IMGSENSOR_ID_SATURATION:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_SATURATION;
        range->maximum       = ISX012_MAX_SATURATION;
        range->step          = ISX012_STEP_SATURATION;
        range->default_value = ISX012_DEF_SATURATION;

        break;

      case IMGSENSOR_ID_HUE:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_HUE;
        range->maximum       = ISX012_MAX_HUE;
        range->step          = ISX012_STEP_HUE;
        range->default_value = ISX012_DEF_HUE;

        break;

      case IMGSENSOR_ID_AUTO_WHITE_BALANCE:
        value->type          = IMGSENSOR_CTRL_TYPE_BOOLEAN;
        range->minimum       = ISX012_MIN_AUTOWB;
        range->maximum       = ISX012_MAX_AUTOWB;
        range->step          = ISX012_STEP_AUTOWB;
        range->default_value = ISX012_DEF_AUTOWB;

        break;
      case IMGSENSOR_ID_GAMMA_CURVE:
        value->type          = IMGSENSOR_CTRL_TYPE_U16;
        elems->minimum       = ISX012_MIN_GAMMACURVE;
        elems->maximum       = ISX012_MAX_GAMMACURVE;
        elems->step          = ISX012_STEP_GAMMACURVE;
        elems->nr_elems      = ISX012_ELEMS_GAMMACURVE;

        break;

      case IMGSENSOR_ID_EXPOSURE:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_EXPOSURE;
        range->maximum       = ISX012_MAX_EXPOSURE;
        range->step          = ISX012_STEP_EXPOSURE;
        range->default_value = ISX012_DEF_EXPOSURE;

        break;

      case IMGSENSOR_ID_HFLIP_VIDEO:
        value->type          = IMGSENSOR_CTRL_TYPE_BOOLEAN;
        range->minimum       = ISX012_MIN_HFLIP;
        range->maximum       = ISX012_MAX_HFLIP;
        range->step          = ISX012_STEP_HFLIP;
        range->default_value = ISX012_DEF_HFLIP;

        break;

      case IMGSENSOR_ID_VFLIP_VIDEO:
        value->type          = IMGSENSOR_CTRL_TYPE_BOOLEAN;
        range->minimum       = ISX012_MIN_VFLIP;
        range->maximum       = ISX012_MAX_VFLIP;
        range->step          = ISX012_STEP_VFLIP;
        range->default_value = ISX012_DEF_VFLIP;

        break;

      case IMGSENSOR_ID_HFLIP_STILL:
        value->type          = IMGSENSOR_CTRL_TYPE_BOOLEAN;
        range->minimum       = ISX012_MIN_HFLIP_STILL;
        range->maximum       = ISX012_MAX_HFLIP_STILL;
        range->step          = ISX012_STEP_HFLIP_STILL;
        range->default_value = ISX012_DEF_HFLIP_STILL;

        break;

      case IMGSENSOR_ID_VFLIP_STILL:
        value->type          = IMGSENSOR_CTRL_TYPE_BOOLEAN;
        range->minimum       = ISX012_MIN_VFLIP_STILL;
        range->maximum       = ISX012_MAX_VFLIP_STILL;
        range->step          = ISX012_STEP_VFLIP_STILL;
        range->default_value = ISX012_DEF_VFLIP_STILL;

        break;

      case IMGSENSOR_ID_SHARPNESS:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_SHARPNESS;
        range->maximum       = ISX012_MAX_SHARPNESS;
        range->step          = ISX012_STEP_SHARPNESS;
        range->default_value = ISX012_DEF_SHARPNESS;

        break;

      case IMGSENSOR_ID_COLOR_KILLER:
        value->type          = IMGSENSOR_CTRL_TYPE_BOOLEAN;
        range->minimum       = ISX012_MIN_COLORKILLER;
        range->maximum       = ISX012_MAX_COLORKILLER;
        range->step          = ISX012_STEP_COLORKILLER;
        range->default_value = ISX012_DEF_COLORKILLER;

        break;

      case IMGSENSOR_ID_COLORFX:
        value->type = IMGSENSOR_CTRL_TYPE_INTEGER_MENU;
        discrete->nr_values
          = ARRAY_NENTRIES(g_isx012_colorfx_actual);
        discrete->values = g_isx012_colorfx_actual;
        discrete->default_value = IMGSENSOR_COLORFX_NONE;

        break;

      case IMGSENSOR_ID_EXPOSURE_AUTO:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_EXPOSUREAUTO;
        range->maximum       = ISX012_MAX_EXPOSUREAUTO;
        range->step          = ISX012_STEP_EXPOSUREAUTO;
        range->default_value = ISX012_DEF_EXPOSUREAUTO;

        break;

      case IMGSENSOR_ID_EXPOSURE_ABSOLUTE:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_EXPOSURETIME;
        range->maximum       = ISX012_MAX_EXPOSURETIME;
        range->step          = ISX012_STEP_EXPOSURETIME;
        range->default_value = ISX012_DEF_EXPOSURETIME;

        break;

      case IMGSENSOR_ID_EXPOSURE_METERING:
        value->type = IMGSENSOR_CTRL_TYPE_INTEGER_MENU;
        discrete->nr_values
          = ARRAY_NENTRIES(g_isx012_photometry_actual);
        discrete->values = g_isx012_photometry_actual;
        discrete->default_value
          = IMGSENSOR_EXPOSURE_METERING_AVERAGE;

        break;

      case IMGSENSOR_ID_SPOT_POSITION:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_SPOTPOS;
        range->maximum       = ISX012_MAX_SPOTPOS;
        range->step          = ISX012_STEP_SPOTPOS;
        range->default_value = ISX012_DEF_SPOTPOS;

        break;

      case IMGSENSOR_ID_AUTO_N_PRESET_WB:
        value->type = IMGSENSOR_CTRL_TYPE_INTEGER_MENU;
        discrete->nr_values = ARRAY_NENTRIES(g_isx012_presetwb_actual);
        discrete->values = g_isx012_presetwb_actual;
        discrete->default_value = IMGSENSOR_WHITE_BALANCE_AUTO;

        break;

      case IMGSENSOR_ID_WIDE_DYNAMIC_RANGE:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_YGAMMA;
        range->maximum       = ISX012_MAX_YGAMMA;
        range->step          = ISX012_STEP_YGAMMA;
        range->default_value = ISX012_DEF_YGAMMA;

        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY:
        value->type             = IMGSENSOR_CTRL_TYPE_INTEGER_MENU;
        discrete->nr_values     = ARRAY_NENTRIES(g_isx012_iso_actual);
        discrete->values        = g_isx012_iso_actual;
        discrete->default_value = 0;

        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY_AUTO:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_ISOAUTO;
        range->maximum       = ISX012_MAX_ISOAUTO;
        range->step          = ISX012_STEP_ISOAUTO;
        range->default_value = ISX012_DEF_ISOAUTO;

        break;

      case IMGSENSOR_ID_3A_LOCK:
        value->type          = IMGSENSOR_CTRL_TYPE_BITMASK;
        range->minimum       = ISX012_MIN_3ALOCK;
        range->maximum       = ISX012_MAX_3ALOCK;
        range->step          = ISX012_STEP_3ALOCK;
        range->default_value = ISX012_DEF_3ALOCK;

        break;

      case IMGSENSOR_ID_3A_PARAMETER:
        value->type          = IMGSENSOR_CTRL_TYPE_U16;
        elems->minimum       = 0;
        elems->maximum       = 65535;
        elems->step          = 1;
        elems->nr_elems      = ISX012_ELEMS_3APARAM;

        break;

      case IMGSENSOR_ID_3A_STATUS:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = 0;
        range->maximum       = 3;
        range->step          = 1;
        range->default_value = 3;

        break;

      case IMGSENSOR_ID_JPEG_QUALITY:
        value->type          = IMGSENSOR_CTRL_TYPE_INTEGER;
        range->minimum       = ISX012_MIN_JPGQUALITY;
        range->maximum       = ISX012_MAX_JPGQUALITY;
        range->step          = ISX012_STEP_JPGQUALITY;
        range->default_value = ISX012_DEF_JPGQUALITY;

        break;

      default: /* Unsupported parameter */
        ret = -EINVAL;

        break;
    }

  return ret;
}

static void get_current_framesize(FAR struct isx012_dev_s *priv,
                                  FAR uint16_t *w,
                                  FAR uint16_t *h)
{
  uint16_t w_addr = HSIZE_MONI;
  uint16_t h_addr = VSIZE_MONI;

  switch (priv->mode)
    {
      case REGVAL_MODESEL_MON:
        w_addr = HSIZE_MONI;
        h_addr = VSIZE_MONI;
        break;

      case REGVAL_MODESEL_MOV:
        w_addr = HSIZE_MOVIE;
        h_addr = VSIZE_MOVIE;
        break;

      case REGVAL_MODESEL_CAP:
        w_addr = HSIZE_CAP;
        h_addr = VSIZE_CAP;
        break;

      default:

        /* It does not come here due to register specification. */

        break;
    }

  *w = isx012_getreg(priv, w_addr, 2);
  *h = isx012_getreg(priv, h_addr, 2);
}

static uint32_t restore_spot_position(uint8_t regval,
                                      uint16_t w,
                                      uint16_t split)
{
  return ((regval * w) / split + (w / split) / 2);
}

static int32_t get_spot_position(FAR struct isx012_dev_s *priv)
{
  uint16_t regval;
  uint16_t reg_x;
  uint16_t reg_y;
  uint32_t x;
  uint32_t y;
  uint16_t w;
  uint16_t h;

  regval = isx012_getreg(priv, SPOT_FRM_NUM, 1);
  reg_x = regval % ISX012_SPOT_POSITION_SPLIT_NUM_X;
  reg_y = regval / ISX012_SPOT_POSITION_SPLIT_NUM_X;

  get_current_framesize(priv, &w, &h);

  x = restore_spot_position(reg_x, w, ISX012_SPOT_POSITION_SPLIT_NUM_X);
  y = restore_spot_position(reg_y, h, ISX012_SPOT_POSITION_SPLIT_NUM_Y);

  return (int32_t)((x << 16) | y);
}

static int isx012_get_value(FAR struct imgsensor_s *sensor,
                            uint32_t id, uint32_t size,
                            FAR imgsensor_value_t *value)
{
  FAR isx012_dev_t *priv = (FAR isx012_dev_t *)sensor;
  uint16_t      readvalue;
  uint8_t       cnt;
  uint8_t       threea_enable;
  uint16_t      read_src;
  FAR uint16_t *read_dst;
  int           ret = OK;
  uint16_t      exposure_time_lsb;
  uint16_t      exposure_time_msb;

  ASSERT(value);

  switch (id)
    {
      case IMGSENSOR_ID_BRIGHTNESS:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_BRIGHTNESS,
                                  ISX012_SIZE_BRIGHTNESS);

        value->value32 = (int32_t)(int8_t)(0x00ff & readvalue);
        break;

      case IMGSENSOR_ID_CONTRAST:
        value->value32 = isx012_getreg(priv,
                                       ISX012_REG_CONTRAST,
                                       ISX012_SIZE_CONTRAST);
        break;

      case IMGSENSOR_ID_SATURATION:
        value->value32 = isx012_getreg(priv,
                                       ISX012_REG_SATURATION,
                                       ISX012_SIZE_SATURATION);
        break;

      case IMGSENSOR_ID_HUE:
        value->value32 = isx012_getreg(priv,
                                       ISX012_REG_HUE,
                                       ISX012_SIZE_HUE);
        break;

      case IMGSENSOR_ID_AUTO_WHITE_BALANCE:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_AUTOWB,
                                  ISX012_SIZE_AUTOWB);

        /* Convert to video driver's value */

        value->value32 = (readvalue & REGVAL_CPUEXT_BIT_AWBSTOP) ? 0 : 1;

        break;

      case IMGSENSOR_ID_GAMMA_CURVE:
        if (value->p_u16 == NULL)
          {
            return -EINVAL;
          }

        if (size != ISX012_ELEMS_GAMMACURVE * sizeof(uint16_t))
          {
            return -EINVAL;
          }

        read_src = ISX012_REG_GAMMACURVE;
        read_dst = value->p_u16;

        for (cnt = 0; cnt < ISX012_ELEMS_GAMMACURVE; cnt++)
          {
            *read_dst = isx012_getreg(priv,
                                      read_src,
                                      ISX012_SIZE_GAMMACURVE);
            read_src += ISX012_SIZE_GAMMACURVE;
            read_dst++;
          }

        break;

      case IMGSENSOR_ID_EXPOSURE:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_EXPOSURE,
                                  ISX012_SIZE_EXPOSURE);

        value->value32 = (int32_t)(int8_t)(0x00ff & readvalue);

        break;

      case IMGSENSOR_ID_HFLIP_VIDEO:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_HFLIP,
                                  ISX012_SIZE_HFLIP);

        value->value32 = (readvalue & REGVAL_READVECT_BIT_H) ? 1 : 0;

        break;

      case IMGSENSOR_ID_VFLIP_VIDEO:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_VFLIP,
                                  ISX012_SIZE_VFLIP);

        value->value32 = (readvalue & REGVAL_READVECT_BIT_V) ? 1 : 0;

        break;

      case IMGSENSOR_ID_HFLIP_STILL:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_HFLIP_STILL,
                                  ISX012_SIZE_HFLIP_STILL);

        value->value32 = (readvalue & REGVAL_READVECT_BIT_H) ? 1 : 0;

        break;

      case IMGSENSOR_ID_VFLIP_STILL:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_VFLIP_STILL,
                                  ISX012_SIZE_VFLIP_STILL);

        value->value32 = (readvalue & REGVAL_READVECT_BIT_V) ? 1 : 0;

        break;

      case IMGSENSOR_ID_SHARPNESS:
        value->value32 = isx012_getreg(priv,
                                       ISX012_REG_SHARPNESS,
                                       ISX012_SIZE_SHARPNESS);
        break;

      case IMGSENSOR_ID_COLOR_KILLER:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_COLORKILLER,
                                  ISX012_SIZE_COLORKILLER);

        value->value32 = (readvalue == REGVAL_EFFECT_MONOTONE) ? 1 : 0;

        break;

      case IMGSENSOR_ID_COLORFX:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_COLOREFFECT,
                                  ISX012_SIZE_COLOREFFECT);

        ret = -EINVAL;
        for (cnt = 0; cnt < ARRAY_NENTRIES(g_isx012_colorfx_regval); cnt++)
          {
            if (g_isx012_colorfx_regval[cnt] == readvalue)
              {
                value->value32 = g_isx012_colorfx_actual[cnt];
                ret = OK;
                break;
              }
          }

        break;

      case IMGSENSOR_ID_EXPOSURE_AUTO:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_EXPOSURETIME,
                                  ISX012_SIZE_EXPOSURETIME);

        value->value32 = readvalue ?
                         IMGSENSOR_EXPOSURE_MANUAL : IMGSENSOR_EXPOSURE_AUTO;

        break;

      case IMGSENSOR_ID_EXPOSURE_ABSOLUTE:
        value->value32 = isx012_getreg(priv,
                                       ISX012_REG_EXPOSURETIME,
                                       ISX012_SIZE_EXPOSURETIME);

        if (value->value32 == REGVAL_EXPOSURETIME_AUTO)
          {
            exposure_time_lsb = isx012_getreg
                                (priv,
                                 ISX012_REG_EXPOSUREAUTOVALUE_LSB,
                                 ISX012_SIZE_EXPOSUREAUTOVALUE);
            exposure_time_msb = isx012_getreg
                                (priv,
                                 ISX012_REG_EXPOSUREAUTOVALUE_MSB,
                                 ISX012_SIZE_EXPOSUREAUTOVALUE);

            value->value32 = (uint16_t)(((exposure_time_msb << 16)
                                         | exposure_time_lsb)
                                        / ISX012_UNIT_EXPOSURETIME_US);
          }
        break;

      case IMGSENSOR_ID_AUTO_N_PRESET_WB:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_PRESETWB,
                                  ISX012_SIZE_PRESETWB);

        for (cnt = 0; cnt < ARRAY_NENTRIES(g_isx012_presetwb_regval); cnt++)
          {
            if (g_isx012_presetwb_regval[cnt] == readvalue)
              {
                value->value32 = g_isx012_presetwb_actual[cnt];
                ret = OK;
                break;
              }
          }

        break;

      case IMGSENSOR_ID_WIDE_DYNAMIC_RANGE:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_YGAMMA,
                                  ISX012_SIZE_YGAMMA);
        value->value32 = readvalue ? 0 : 1;

        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_ISOAUTOVALUE,
                                  ISX012_SIZE_ISOAUTOVALUE);

        ret = -EINVAL;
        for (cnt = 0; cnt < ARRAY_NENTRIES(g_isx012_iso_regval); cnt++)
          {
            if (g_isx012_iso_regval[cnt] == readvalue)
              {
                value->value32 = g_isx012_iso_actual[cnt];
                ret = OK;
                break;
              }
          }

        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY_AUTO:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_ISOAUTO,
                                  ISX012_SIZE_ISOAUTO);

        value->value32 = (readvalue == REGVAL_ISO_AUTO) ?
                         IMGSENSOR_ISO_SENSITIVITY_AUTO :
                         IMGSENSOR_ISO_SENSITIVITY_MANUAL;

        break;

      case IMGSENSOR_ID_EXPOSURE_METERING:
        readvalue = isx012_getreg(priv,
                                  ISX012_REG_PHOTOMETRY,
                                  ISX012_SIZE_PHOTOMETRY);

        ret = -EINVAL;
        for (cnt = 0;
             cnt < ARRAY_NENTRIES(g_isx012_photometry_regval);
             cnt++)
          {
            if (g_isx012_photometry_regval[cnt] == readvalue)
              {
                value->value32 = g_isx012_photometry_actual[cnt];
                ret = OK;
                break;
              }
          }

        break;

      case IMGSENSOR_ID_SPOT_POSITION:
        value->value32 = get_spot_position(priv);
        break;

      case IMGSENSOR_ID_3A_PARAMETER:
        if (value->p_u16 == NULL)
          {
            return -EINVAL;
          }

        if (size != ISX012_ELEMS_3APARAM * sizeof(uint16_t))
          {
            return -EINVAL;
          }

        /* Get AWB parameter */

        value->p_u16[0] = isx012_getreg(priv, RATIO_R, 2);
        value->p_u16[1] = isx012_getreg(priv, RATIO_B, 2);

        /* Get AE parameter */

        value->p_u16[2] = isx012_getreg(priv, AELEVEL, 2);

        break;

      case IMGSENSOR_ID_3A_STATUS:

        /* Initialize returned status */

        value->value32 = IMGSENSOR_3A_STATUS_STABLE;

        /* Get AWB/AE enable or not */

        threea_enable = isx012_getreg(priv, CPUEXT, 1);

        if ((threea_enable & REGVAL_CPUEXT_BIT_AWBSTOP)
               != REGVAL_CPUEXT_BIT_AWBSTOP)
          {
            readvalue = isx012_getreg(priv, AWBSTS, 1);
            if (readvalue != REGVAL_AWBSTS_STOP) /* AWB is not stopped */
              {
                value->value32 |= IMGSENSOR_3A_STATUS_AWB_OPERATING;
              }
          }

        if ((threea_enable & REGVAL_CPUEXT_BIT_AESTOP)
               != REGVAL_CPUEXT_BIT_AESTOP)
          {
            readvalue = isx012_getreg(priv, AESTS, 1);
            if (readvalue != REGVAL_AESTS_STOP) /* AE is not stopped */
              {
                value->value32 |= IMGSENSOR_3A_STATUS_AE_OPERATING;
              }
          }
        break;

      case IMGSENSOR_ID_JPEG_QUALITY:
        value->value32 = isx012_getreg(priv,
                                       ISX012_REG_JPGQUALITY,
                                       ISX012_SIZE_JPGQUALITY);
        break;

      default: /* Unsupported id */

        ret = -EINVAL;
        break;
    }

  return ret;
}

static bool validate_clip_setting(uint32_t sz, FAR uint32_t *clip)
{
  bool ret = false;
  uint32_t w;
  uint32_t h;

  DEBUGASSERT(clip);

  if (sz != IMGSENSOR_CLIP_NELEM * sizeof(uint32_t))
    {
      return ret;
    }

  w = clip[IMGSENSOR_CLIP_INDEX_WIDTH];
  h = clip[IMGSENSOR_CLIP_INDEX_HEIGHT];

  if ((w % CLIP_SIZE_UNIT == 0) && (h % CLIP_SIZE_UNIT == 0))
    {
      ret = true;
    }

  return ret;
}

static int set_clip(uint32_t size,
                    FAR uint32_t *val,
                    FAR isx012_rect_t *target)
{
  DEBUGASSERT(target);

  if (!validate_clip_setting(size, val))
    {
      return -EINVAL;
    }

  target->left   = (int32_t)val[IMGSENSOR_CLIP_INDEX_LEFT];
  target->top    = (int32_t)val[IMGSENSOR_CLIP_INDEX_TOP];
  target->width  = val[IMGSENSOR_CLIP_INDEX_WIDTH];
  target->height = val[IMGSENSOR_CLIP_INDEX_HEIGHT];

  return OK;
}

static int set_spot_position(FAR struct isx012_dev_s *priv, int32_t val)
{
  uint16_t w;
  uint16_t h;
  uint16_t x = (uint16_t)(val >> 16);
  uint16_t y = (uint16_t)(val & 0xffff);
  uint8_t reg_x;
  uint8_t reg_y;
  uint8_t reg;

  get_current_framesize(priv, &w, &h);

  if ((x >= w) || (y >= h))
    {
      return -EINVAL;
    }

  reg_x = (x * ISX012_SPOT_POSITION_SPLIT_NUM_X) / w;
  reg_y = (y * ISX012_SPOT_POSITION_SPLIT_NUM_Y) / h;

  reg = reg_y * ISX012_SPOT_POSITION_SPLIT_NUM_X + reg_x;

  return isx012_putreg(priv, SPOT_FRM_NUM, reg, 1);
}

static int isx012_set_value(FAR struct imgsensor_s *sensor,
                            uint32_t id, uint32_t size,
                            imgsensor_value_t value)
{
  FAR isx012_dev_t *priv = (FAR isx012_dev_t *)sensor;
  int           ret = -EINVAL;
  uint8_t       cnt;
  FAR uint16_t *write_src;
  uint16_t      write_dst;
  uint16_t      regval;
  uint16_t      exposure_time_lsb;
  uint16_t      exposure_time_msb;

  switch (id)
    {
      case IMGSENSOR_ID_BRIGHTNESS:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_BRIGHTNESS,
                             ISX012_MAX_BRIGHTNESS,
                             ISX012_STEP_BRIGHTNESS);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_BRIGHTNESS,
                            value.value32,
                            ISX012_SIZE_BRIGHTNESS);
        break;

      case IMGSENSOR_ID_CONTRAST:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_CONTRAST,
                             ISX012_MAX_CONTRAST,
                             ISX012_STEP_CONTRAST);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_CONTRAST,
                            value.value32,
                            ISX012_SIZE_CONTRAST);
        break;

      case IMGSENSOR_ID_SATURATION:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_SATURATION,
                             ISX012_MAX_SATURATION,
                             ISX012_STEP_SATURATION);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_SATURATION,
                            value.value32,
                            ISX012_SIZE_SATURATION);
        break;

      case IMGSENSOR_ID_HUE:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_HUE,
                             ISX012_MAX_HUE,
                             ISX012_STEP_HUE);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_HUE,
                            value.value32,
                            ISX012_SIZE_HUE);
        break;

      case IMGSENSOR_ID_AUTO_WHITE_BALANCE:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_AUTOWB,
                             ISX012_MAX_AUTOWB,
                             ISX012_STEP_AUTOWB);
        if (ret != OK)
          {
            break;
          }

        regval = isx012_getreg(priv,
                               ISX012_REG_AUTOWB,
                               ISX012_SIZE_AUTOWB);

        if (value.value32)
          {
            /* Because true means setting auto white balance
             * turn off the stop bit
             */

            regval &= ~REGVAL_CPUEXT_BIT_AWBSTOP;
          }
        else
          {
            /* Because false means stopping auto white balance,
             * turn on the stop bit.
             */

            regval |= REGVAL_CPUEXT_BIT_AWBSTOP;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_AUTOWB,
                            regval,
                            ISX012_SIZE_AUTOWB);
        break;

      case IMGSENSOR_ID_GAMMA_CURVE:
        if (value.p_u16 == NULL)
          {
            return -EINVAL;
          }

        if (size != ISX012_ELEMS_GAMMACURVE * 2)
          {
            return -EINVAL;
          }

        write_src = value.p_u16;
        write_dst = ISX012_REG_GAMMACURVE;

        for (cnt = 0; cnt < ISX012_ELEMS_GAMMACURVE; cnt++)
          {
            ret = VALIDATE_VALUE(*write_src,
                                 ISX012_MIN_GAMMACURVE,
                                 ISX012_MAX_GAMMACURVE,
                                 ISX012_STEP_GAMMACURVE);
            if (ret != OK)
              {
                break;
              }

            ret = isx012_putreg(priv,
                                write_dst,
                                *write_src,
                                ISX012_SIZE_GAMMACURVE);

            write_src++;
            write_dst += ISX012_SIZE_GAMMACURVE;
          }

        break;

      case IMGSENSOR_ID_EXPOSURE:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_EXPOSURE,
                             ISX012_MAX_EXPOSURE,
                             ISX012_STEP_EXPOSURE);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_EXPOSURE,
                            value.value32,
                            ISX012_SIZE_EXPOSURE);

        break;

      case IMGSENSOR_ID_HFLIP_VIDEO:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_HFLIP,
                             ISX012_MAX_HFLIP,
                             ISX012_STEP_HFLIP);
        if (ret != OK)
          {
            break;
          }

        regval = isx012_getreg(priv,
                               ISX012_REG_HFLIP,
                               ISX012_SIZE_HFLIP);

        if (value.value32)
          {
            regval |= REGVAL_READVECT_BIT_H;
          }
        else
          {
            regval &= ~REGVAL_READVECT_BIT_H;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_HFLIP,
                            regval,
                            ISX012_SIZE_HFLIP);
        break;

      case IMGSENSOR_ID_VFLIP_VIDEO:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_VFLIP,
                             ISX012_MAX_VFLIP,
                             ISX012_STEP_VFLIP);
        if (ret != OK)
          {
            break;
          }

        regval = isx012_getreg(priv,
                               ISX012_REG_VFLIP,
                               ISX012_SIZE_VFLIP);

        if (value.value32)
          {
            regval |= REGVAL_READVECT_BIT_V;
          }
        else
          {
            regval &= ~REGVAL_READVECT_BIT_V;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_VFLIP,
                            regval,
                            ISX012_SIZE_VFLIP);
        break;

      case IMGSENSOR_ID_HFLIP_STILL:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_HFLIP_STILL,
                             ISX012_MAX_HFLIP_STILL,
                             ISX012_STEP_HFLIP_STILL);
        if (ret != OK)
          {
            break;
          }

        regval = isx012_getreg(priv,
                               ISX012_REG_HFLIP_STILL,
                               ISX012_SIZE_HFLIP_STILL);

        if (value.value32)
          {
            regval |= REGVAL_READVECT_BIT_H;
          }
        else
          {
            regval &= ~REGVAL_READVECT_BIT_H;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_HFLIP_STILL,
                            regval,
                            ISX012_SIZE_HFLIP_STILL);

        break;

      case IMGSENSOR_ID_VFLIP_STILL:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_VFLIP_STILL,
                             ISX012_MAX_VFLIP_STILL,
                             ISX012_STEP_VFLIP_STILL);
        if (ret != OK)
          {
            break;
          }

        regval = isx012_getreg(priv,
                               ISX012_REG_VFLIP_STILL,
                               ISX012_SIZE_VFLIP_STILL);

        if (value.value32)
          {
            regval |= REGVAL_READVECT_BIT_V;
          }
        else
          {
            regval &= ~REGVAL_READVECT_BIT_V;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_VFLIP_STILL,
                            regval,
                            ISX012_SIZE_VFLIP_STILL);
        break;

      case IMGSENSOR_ID_SHARPNESS:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_SHARPNESS,
                             ISX012_MAX_SHARPNESS,
                             ISX012_STEP_SHARPNESS);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_SHARPNESS,
                            value.value32,
                            ISX012_SIZE_SHARPNESS);
        break;

      case IMGSENSOR_ID_COLOR_KILLER:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_COLORKILLER,
                             ISX012_MAX_COLORKILLER,
                             ISX012_STEP_COLORKILLER);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg
                (priv,
                 ISX012_REG_COLORKILLER,
                 value.value32 ? REGVAL_EFFECT_MONOTONE : REGVAL_EFFECT_NONE,
                 ISX012_SIZE_COLORKILLER);

        break;

      case IMGSENSOR_ID_COLORFX:
        for (cnt = 0; cnt < ARRAY_NENTRIES(g_isx012_colorfx_actual); cnt++)
          {
            if (g_isx012_colorfx_actual[cnt] == value.value32)
              {
                ret = isx012_putreg(priv,
                                    ISX012_REG_COLOREFFECT,
                                    g_isx012_colorfx_regval[cnt],
                                    ISX012_SIZE_COLOREFFECT);
                break;
              }
          }

        break;

      case IMGSENSOR_ID_EXPOSURE_AUTO:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_EXPOSUREAUTO,
                             ISX012_MAX_EXPOSUREAUTO,
                             ISX012_STEP_EXPOSUREAUTO);
        if (ret != OK)
          {
            break;
          }

        if (value.value32 == IMGSENSOR_EXPOSURE_AUTO)
          {
            /* Register is the same as IMGSENSOR_ID_EXPOSURE_ABSOLUTE.
             * If this register value = REGVAL_EXPOSURETIME_AUTO(=0),
             *  it means auto. Otherwise, it means manual.
             */

            ret = isx012_putreg(priv,
                                ISX012_REG_EXPOSURETIME,
                                REGVAL_EXPOSURETIME_AUTO,
                                ISX012_SIZE_EXPOSURETIME);
          }
        else
          {
            /* In manual case, read current value of register which
             * value adjusted automatically by ISX012 HW is set to.
             * It has 32bits length which is composed of LSB 16bits
             *  and MSB 16bits.
             */

            exposure_time_lsb = isx012_getreg
                                (priv,
                                 ISX012_REG_EXPOSUREAUTOVALUE_LSB,
                                 ISX012_SIZE_EXPOSUREAUTOVALUE);
            exposure_time_msb = isx012_getreg
                                (priv,
                                 ISX012_REG_EXPOSUREAUTOVALUE_MSB,
                                 ISX012_SIZE_EXPOSUREAUTOVALUE);

            /* Register value adjusted automatically by ISX012 HW
             *  has the different unit from manual value register.
             *   automatic value register : 1   microsec unit
             *   manual    value register : 100 microsec unit
             */

            regval = (uint16_t)(((exposure_time_msb << 16)
                                  | exposure_time_lsb)
                                 / ISX012_UNIT_EXPOSURETIME_US);
            ret = isx012_putreg(priv,
                                ISX012_REG_EXPOSURETIME,
                                regval,
                                ISX012_SIZE_EXPOSURETIME);
          }

        break;

      case IMGSENSOR_ID_EXPOSURE_ABSOLUTE:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_EXPOSURETIME,
                             ISX012_MAX_EXPOSURETIME,
                             ISX012_STEP_EXPOSURETIME);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_EXPOSURETIME,
                            value.value32,
                            ISX012_SIZE_EXPOSURETIME);
        break;

      case IMGSENSOR_ID_WIDE_DYNAMIC_RANGE:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_YGAMMA,
                             ISX012_MAX_YGAMMA,
                             ISX012_STEP_YGAMMA);
        if (ret != OK)
          {
            break;
          }

        if (value.value32)
          {
            regval = REGVAL_YGAMMA_AUTO;
          }
        else
          {
            regval = REGVAL_YGAMMA_OFF;
          }

        ret = isx012_putreg
                (priv,
                 ISX012_REG_YGAMMA,
                 value.value32 ? REGVAL_YGAMMA_AUTO : REGVAL_YGAMMA_OFF,
                 ISX012_SIZE_YGAMMA);

        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY:
        for (cnt = 0; cnt < ARRAY_NENTRIES(g_isx012_iso_actual); cnt++)
          {
            if (g_isx012_iso_actual[cnt] == value.value32)
              {
                ret = isx012_putreg(priv,
                                    ISX012_REG_ISO,
                                    g_isx012_iso_regval[cnt],
                                    ISX012_SIZE_ISO);
                break;
              }
          }

        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY_AUTO:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_ISOAUTO,
                             ISX012_MAX_ISOAUTO,
                             ISX012_STEP_ISOAUTO);
        if (ret != OK)
          {
            break;
          }

        if (value.value32 == IMGSENSOR_ISO_SENSITIVITY_AUTO)
          {
            ret = isx012_putreg(priv,
                                ISX012_REG_ISOAUTO,
                                REGVAL_ISO_AUTO,
                                ISX012_SIZE_ISOAUTO);
          }
        else
          {
            /* In manual case, read auto adjust value and set it */

            regval = isx012_getreg(priv,
                                   ISX012_REG_ISOAUTOVALUE,
                                   ISX012_SIZE_ISOAUTOVALUE);
            ret = isx012_putreg(priv,
                                ISX012_REG_ISO,
                                regval,
                                ISX012_SIZE_ISO);
          }

        break;

      case IMGSENSOR_ID_EXPOSURE_METERING:
        for (cnt = 0;
             cnt < ARRAY_NENTRIES(g_isx012_photometry_actual);
             cnt++)
          {
            if (g_isx012_photometry_actual[cnt] == value.value32)
              {
                ret = isx012_putreg(priv,
                                    ISX012_REG_PHOTOMETRY,
                                    g_isx012_photometry_regval[cnt],
                                    ISX012_SIZE_PHOTOMETRY);
                break;
              }
          }

        break;

      case IMGSENSOR_ID_SPOT_POSITION:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_SPOTPOS,
                             ISX012_MAX_SPOTPOS,
                             ISX012_STEP_SPOTPOS);
        if (ret != OK)
          {
            break;
          }

        ret = set_spot_position(priv, value.value32);
        break;

      case IMGSENSOR_ID_AUTO_N_PRESET_WB:
        for (cnt = 0;
             cnt < ARRAY_NENTRIES(g_isx012_presetwb_actual);
             cnt++)
          {
            if (g_isx012_presetwb_actual[cnt] == value.value32)
              {
                ret = isx012_putreg(priv,
                                    ISX012_REG_PRESETWB,
                                    g_isx012_presetwb_regval[cnt],
                                    ISX012_SIZE_PRESETWB);
                break;
              }
          }

        break;

      case IMGSENSOR_ID_3A_LOCK:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_3ALOCK,
                             ISX012_MAX_3ALOCK,
                             ISX012_STEP_3ALOCK);
        if (ret != OK)
          {
            break;
          }

        regval = 0;

        if ((value.value32 & IMGSENSOR_LOCK_EXPOSURE)
              == IMGSENSOR_LOCK_EXPOSURE)
          {
            regval |= REGVAL_CPUEXT_BIT_AESTOP;
          }

        if ((value.value32 & IMGSENSOR_LOCK_WHITE_BALANCE)
              == IMGSENSOR_LOCK_WHITE_BALANCE)
          {
            regval |= REGVAL_CPUEXT_BIT_AWBSTOP;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_3ALOCK,
                            regval,
                            ISX012_SIZE_3ALOCK);

        break;

      case IMGSENSOR_ID_3A_PARAMETER:

        /* AWB parameter : red */

        ret = isx012_putreg(priv, INIT_CONT_INR, value.p_u16[0], 2);
        ret = isx012_putreg(priv, INIT_CONT_OUTR, value.p_u16[0], 2);

        /* AWB parameter : blue */

        ret = isx012_putreg(priv, INIT_CONT_INB, value.p_u16[1], 2);
        ret = isx012_putreg(priv, INIT_CONT_OUTB, value.p_u16[1], 2);

        /* AE parameter */

        ret = isx012_putreg(priv, AE_START_LEVEL, value.p_u16[2], 2);

        break;

      case IMGSENSOR_ID_JPEG_QUALITY:
        ret = VALIDATE_VALUE(value.value32,
                             ISX012_MIN_JPGQUALITY,
                             ISX012_MAX_JPGQUALITY,
                             ISX012_STEP_JPGQUALITY);
        if (ret != OK)
          {
            break;
          }

        ret = isx012_putreg(priv,
                            ISX012_REG_JPGQUALITY,
                            value.value32,
                            ISX012_SIZE_JPGQUALITY);
        break;

      case IMGSENSOR_ID_CLIP_VIDEO:
        ret = set_clip(size, value.p_u32, &priv->clip_video);

        break;

      case IMGSENSOR_ID_CLIP_STILL:
        ret = set_clip(size, value.p_u32, &priv->clip_still);

        break;

      default: /* Unsupported control id */

        break;
    }

  return ret;
}

static int isx012_set_shd(FAR isx012_dev_t *priv)
{
  int ret;
  int unit_cnt;
  int size_cnt;

  /* At first, disable CXC and SHD */

  ret = isx012_putreg(priv, SHD_EN, 0x50, 1);
  if (ret < 0)
    {
      verr("isx012_putreg(disable CXC/SHD) failed: %d\n", ret);
      return ret;
    }

  /* Set CXC Validity */

  ret = isx012_putreg(priv, CXC_VALID, 0x8282, 2);
  if (ret < 0)
    {
      verr("isx012_putreg(CXC_VALID) failed: %d\n", ret);
      return ret;
    }

  /* Set CXC R Gb data */

  for (unit_cnt = 0; unit_cnt < CXC_RGB_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < CXC_RGB_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              CXC_RGB_UNIT(unit_cnt, size_cnt),
                              g_isx012_cxc_rgb_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(CXC R Gb) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set CXC G Rb data */

  for (unit_cnt = 0; unit_cnt < CXC_GRB_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < CXC_GRB_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              CXC_GRB_UNIT(unit_cnt, size_cnt),
                              g_isx012_cxc_grb_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(CXC G Rb) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set SHD Validity */

  ret = isx012_putreg(priv, SHD_VALID, 0x9191, 2);
  if (ret < 0)
    {
      verr("isx012_putreg(SHD_VALID) failed: %d\n", ret);
      return ret;
    }

  /* Set SHD R Gb data */

  for (unit_cnt = 0; unit_cnt < SHD_RGB_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < SHD_RGB_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              SHD_RGB_UNIT(unit_cnt, size_cnt),
                              g_isx012_shd_rgb_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(SHD R Gb) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set SHD G Rb data */

  for (unit_cnt = 0; unit_cnt < SHD_GRB_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < SHD_GRB_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              SHD_GRB_UNIT(unit_cnt, size_cnt),
                              g_isx012_shd_grb_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(SHD G Rb) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set SHD R1 data */

  for (unit_cnt = 0; unit_cnt < SHD_R1_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < SHD_R1_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              SHD_R1_UNIT(unit_cnt, size_cnt),
                              g_isx012_shd_r1_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(SHD R1) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set SHD R2 data */

  for (unit_cnt = 0; unit_cnt < SHD_R2_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < SHD_R2_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              SHD_R2_UNIT(unit_cnt, size_cnt),
                              g_isx012_shd_r2_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(SHD R2) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set SHD B2 data */

  for (unit_cnt = 0; unit_cnt < SHD_B2_DATA_UNIT_NUM; unit_cnt++)
    {
      for (size_cnt = 0; size_cnt < SHD_B2_DATA_UNIT_SIZE; size_cnt++)
        {
          ret = isx012_putreg(priv,
                              SHD_B2_UNIT(unit_cnt, size_cnt),
                              g_isx012_shd_b2_data[unit_cnt][size_cnt],
                              1);
          if (ret < 0)
            {
              verr("isx012_putreg(SHD B2) failed: %d\n", ret);
              return ret;
            }
        }
    }

  /* Set SHD thresholds data */

  ret = isx012_putreglist(priv, g_isx012_shd_thresholds,
                          ISX012_SHD_THRESHOLDS_NENTRIES);
  if (ret < 0)
    {
      verr("isx012_putreglist failed(SHD thresholds): %d\n", ret);
      board_isx012_set_reset();
      return ret;
    }

  /* Set SHD white balance data */

  ret = isx012_putreglist(priv, g_isx012_shd_wb, ISX012_SHD_WB_NENTRIES);
  if (ret < 0)
    {
      verr("isx012_putreglist(SHD white balance) failed: %d\n", ret);
      board_isx012_set_reset();
      return ret;
    }

  /* Enable CXC and SHD */

  ret = isx012_putreg(priv, SHD_EN, 0x57, 1);
  if (ret < 0)
    {
      verr("isx012_putreg(enable CXC/SHD) failed: %d\n", ret);
      return ret;
    }

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int isx012_initialize(void)
{
  FAR isx012_dev_t *priv = &g_isx012_private;
  int ret;

  /* Register image sensor operations variable */

  ret = imgsensor_register(&priv->sensor);
  if (ret != OK)
    {
      verr("Failed to register ops to video driver.\n");
      return ret;
    }

  /* Initialize other information */

  priv->state = STATE_ISX012_POWEROFF;
  return OK;
}

int isx012_uninitialize(void)
{
  /* No procedure */

  return OK;
}

#ifdef CONFIG_VIDEO_ISX012_REGDEBUG
int isx012_read_register(uint16_t addr, FAR uint8_t *buf, uint8_t size)
{
  FAR isx012_dev_t *priv = &g_isx012_private;
  uint16_t buf16;

  if (buf == NULL)
    {
      return -EINVAL;
    }

  if (size > 2)
    {
      return -EINVAL;
    }

  buf16 = isx012_getreg(priv, addr, size);
  memcpy(buf, &buf16, size);
  return OK;
}
#endif
